Index: modules/aggregator/aggregator.test =================================================================== RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.test,v retrieving revision 1.26 diff -u -p -r1.26 aggregator.test --- modules/aggregator/aggregator.test 12 Jun 2009 08:39:35 -0000 1.26 +++ modules/aggregator/aggregator.test 9 Jul 2009 10:36:52 -0000 @@ -252,7 +252,7 @@ EOF; for ($i = 0; $i < 5; $i++) { $edit = array(); $edit['title'] = $this->randomName(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $this->drupalPost('node/add/article', $edit, t('Save')); } } Index: modules/blog/blog.test =================================================================== RCS file: /cvs/drupal/drupal/modules/blog/blog.test,v retrieving revision 1.13 diff -u -p -r1.13 blog.test --- modules/blog/blog.test 12 Jun 2009 08:39:35 -0000 1.13 +++ modules/blog/blog.test 9 Jul 2009 10:36:52 -0000 @@ -154,7 +154,7 @@ class BlogTestCase extends DrupalWebTest // Edit blog node. $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body[0][value]'] = $this->randomName(256); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(256); $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Blog entry %title has been updated.', array('%title' => $edit['title'])), t('Blog node was edited')); Index: modules/blogapi/blogapi.module =================================================================== RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v retrieving revision 1.158 diff -u -p -r1.158 blogapi.module --- modules/blogapi/blogapi.module 5 Jul 2009 18:00:07 -0000 1.158 +++ modules/blogapi/blogapi.module 9 Jul 2009 10:36:52 -0000 @@ -213,7 +213,7 @@ function blogapi_blogger_new_post($appke } else { $edit['title'] = blogapi_blogger_title($content); - $edit['body'][0]['value'] = $content; + $edit['body'][FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content; } if (!node_access('create', $edit['type'])) { @@ -274,12 +274,12 @@ function blogapi_blogger_edit_post($appk // Check for bloggerAPI vs. metaWeblogAPI. if (is_array($content)) { $node->title = $content['title']; - $node->body[0]['value'] = $content['description']; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content['description']; _blogapi_mt_extra($node, $content); } else { $node->title = blogapi_blogger_title($content); - $node->body[0]['value'] = $content; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $content; } module_invoke_all('node_blogapi_edit', $node); @@ -891,14 +891,14 @@ function _blogapi_mt_extra($node, $struc // Merge the 3 body sections (description, mt_excerpt, mt_text_more) into one body. if ($struct['mt_excerpt']) { - $node->body[0]['value'] = $struct['mt_excerpt'] . '' . $node->body[0]['value']; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $struct['mt_excerpt'] . '' . $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']; } if ($struct['mt_text_more']) { - $node->body[0]['value'] = $node->body[0]['value'] . '' . $struct['mt_text_more']; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] . '' . $struct['mt_text_more']; } if ($struct['mt_convert_breaks']) { - $node->body[0]['format'] = $struct['mt_convert_breaks']; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format'] = $struct['mt_convert_breaks']; } if ($struct['dateCreated']) { @@ -921,8 +921,8 @@ function _blogapi_get_post($node, $bodie ); if ($bodies) { - $body = $node->body[0]['value']; - $format = $node->body[0]['format']; + $body = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']; + $format = $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format']; if ($node->comment == 1) { $comment = 2; } Index: modules/book/book.test =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.test,v retrieving revision 1.11 diff -u -p -r1.11 book.test --- modules/book/book.test 12 Jun 2009 08:39:36 -0000 1.11 +++ modules/book/book.test 9 Jul 2009 10:36:52 -0000 @@ -141,7 +141,7 @@ class BookTestCase extends DrupalWebTest // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); $this->assertText($node->title, t('Printer friendly title found.')); - $this->assertRaw(check_markup($node->body[0]['value'], $node->body[0]['format']), t('Printer friendly body found.')); + $this->assertRaw(check_markup($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'], $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format']), t('Printer friendly body found.')); $number++; } @@ -173,7 +173,7 @@ class BookTestCase extends DrupalWebTest $edit = array(); $edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10); - $edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); $edit['book[bid]'] = $book_nid; if ($parent !== NULL) { Index: modules/dblog/dblog.test =================================================================== RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v retrieving revision 1.21 diff -u -p -r1.21 dblog.test --- modules/dblog/dblog.test 12 Jun 2009 08:39:36 -0000 1.21 +++ modules/dblog/dblog.test 9 Jul 2009 10:36:52 -0000 @@ -327,7 +327,7 @@ class DBLogTestCase extends DrupalWebTes default: $content = array( 'title' => $this->randomName(8), - 'body[0][value]' => $this->randomName(32), + 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $this->randomName(32), ); break; } @@ -351,7 +351,7 @@ class DBLogTestCase extends DrupalWebTes default: $content = array( - 'body[0][value]' => $this->randomName(32), + 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $this->randomName(32), ); break; } Index: modules/field/field.attach.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v retrieving revision 1.29 diff -u -p -r1.29 field.attach.inc --- modules/field/field.attach.inc 7 Jul 2009 09:28:07 -0000 1.29 +++ modules/field/field.attach.inc 9 Jul 2009 14:34:07 -0000 @@ -172,6 +172,7 @@ function _field_invoke($op, $obj_type, $ // Merge default options. $default_options = array( 'default' => FALSE, + 'language' => NULL ); $options += $default_options; @@ -184,32 +185,38 @@ function _field_invoke($op, $obj_type, $ // When in 'single field' mode, only act on the specified field. if (empty($options['field_name']) || $options['field_name'] == $field_name) { $field = field_info_field($field_name); + $field_translations = array(); + $suggested_languages = empty($options['language']) ? NULL : array($options['language']); - // Extract the field values into a separate variable, easily accessed by - // hook implementations. - $items = isset($object->$field_name) ? $object->$field_name : array(); + // Initialize field translations according to the available languages. + foreach (_field_available_languages($obj_type, $field, $suggested_languages) as $language) { + $field_translations[$language] = isset($object->{$field_name}[$language]) ? $object->{$field_name}[$language] : array(); + } // Invoke the field hook and collect results. $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (drupal_function_exists($function)) { - $result = $function($obj_type, $object, $field, $instance, $items, $a, $b); - if (isset($result)) { - // For hooks with array results, we merge results together. - // For hooks with scalar results, we collect results in an array. - if (is_array($result)) { - $return = array_merge($return, $result); + // Iterate over all the field translations. + foreach ($field_translations as $language => $items) { + $result = $function($obj_type, $object, $field, $instance, $language, $items, $a, $b); + if (isset($result)) { + // For hooks with array results, we merge results together. + // For hooks with scalar results, we collect results in an array. + if (is_array($result)) { + $return = array_merge($return, $result); + } + else { + $return[] = $result; + } } - else { - $return[] = $result; + + // Populate $items back in the field values, but avoid replacing missing + // fields with an empty array (those are not equivalent on update). + if ($items !== array() || isset($object->{$field_name}[$language])) { + $object->{$field_name}[$language] = $items; } } } - - // Populate field values back in the object, but avoid replacing missing - // fields with an empty array (those are not equivalent on update). - if ($items !== array() || property_exists($object, $field_name)) { - $object->$field_name = $items; - } } } @@ -254,6 +261,7 @@ function _field_invoke_multiple($op, $ob // Merge default options. $default_options = array( 'default' => FALSE, + 'language' => NULL ); $options += $default_options; @@ -277,10 +285,11 @@ function _field_invoke_multiple($op, $ob } // Group the corresponding instances and objects. $grouped_instances[$field_name][$id] = $instance; - $grouped_objects[$field_name][$id] = $objects[$id]; - // Extract the field values into a separate variable, easily accessed - // by hook implementations. - $grouped_items[$field_name][$id] = isset($object->$field_name) ? $object->$field_name : array(); + $grouped_objects[$field_name][$id] = &$objects[$id]; + $suggested_languages = empty($options['language']) ? NULL : array($options['language']); + foreach (_field_available_languages($obj_type, $fields[$field_name], $suggested_languages) as $language) { + $grouped_items[$field_name][$language][$id] = isset($object->{$field_name}[$language]) ? $object->{$field_name}[$language] : array(); + } } } // Initialize the return value for each object. @@ -291,17 +300,20 @@ function _field_invoke_multiple($op, $ob foreach ($fields as $field_name => $field) { $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (drupal_function_exists($function)) { - $results = $function($obj_type, $grouped_objects[$field_name], $field, $grouped_instances[$field_name], $grouped_items[$field_name], $a, $b); - if (isset($results)) { - // Collect results by object. - // For hooks with array results, we merge results together. - // For hooks with scalar results, we collect results in an array. - foreach ($results as $id => $result) { - if (is_array($result)) { - $return[$id] = array_merge($return[$id], $result); - } - else { - $return[$id][] = $result; + // Iterate over all the field translations. + foreach ($grouped_items[$field_name] as $language => $items) { + $results = $function($obj_type, $grouped_objects[$field_name], $field, $grouped_instances[$field_name], $language, $grouped_items[$field_name][$language], $a, $b); + if (isset($results)) { + // Collect results by object. + foreach ($results as $id => $result) { + // For hooks with array results, we merge results together. + // For hooks with scalar results, we collect results in an array. + if (is_array($result)) { + $return[$id] = array_merge($return[$id], $result); + } + else { + $return[$id][] = $result; + } } } } @@ -310,8 +322,10 @@ function _field_invoke_multiple($op, $ob // Populate field values back in the objects, but avoid replacing missing // fields with an empty array (those are not equivalent on update). foreach ($grouped_objects[$field_name] as $id => $object) { - if ($grouped_items[$field_name][$id] !== array() || property_exists($object, $field_name)) { - $object->$field_name = $grouped_items[$field_name][$id]; + foreach ($grouped_items[$field_name] as $language => $items) { + if ($grouped_items[$field_name][$language][$id] !== array() || isset($object->{$field_name}[$language])) { + $object->{$field_name}[$language] = $grouped_items[$field_name][$language][$id]; + } } } } @@ -348,6 +362,70 @@ function _field_invoke_multiple_default( } /** + * Collect the available languages for the given entity type and field. + * + * If an entity is declared as translatable and the given field is translatable + * a (not necessarily strict) subset of the current enabled languages will be returned, + * otherwise only FIELD_LANGUAGE_NEUTRAL will be returned. This way, since the default + * value for the "translatble" entity property is FALSE, we ensure that only entities + * able to handle translations actually get translatable fields. + * + * @param $obj_type + * The type of $object; e.g. 'node' or 'user'. + * @param $field + * A field structure. + * @param $suggested_languages + * An array of language preferences which will be intersected with the enabled languages. + * @returns + * An array of valid language codes. + */ +function _field_available_languages($obj_type, $field, $suggested_languages = NULL) { + $field_languages = &drupal_static(__FUNCTION__, array()); + + $field_name = $field['field_name']; + if (!isset($field_languages[$field_name])) { + // If an entity is declared as translatable and the current field is translatable, + // the available languages can be returned otherwise we return FIELD_LANGUAGE_NEUTRAL. + // This way, since the default value for the "translatble" entity property is FALSE, we + // ensure that only entities able to handle translations actually get translatable fields. + $fieldable_info = field_info_fieldable_types($obj_type); + if ($fieldable_info['translatable'] && $field['translatable']) { + $available_languages = array_keys(language_list()); + // The returned languages are a subset of the intersection of enabled ones and suggested ones. + $languages = !empty($suggested_languages) ? $available_languages = array_intersect($available_languages, $suggested_languages) : $available_languages; + foreach (module_implements('field_languages') as $module) { + $function = $module . '_field_languages'; + $function($obj_type, $field, $languages); + } + // TODO: Consider distinguishing between content language and UI language, + // probably they should not be tied in any way. + // Accept only available languages. + $field_languages[$field_name] = array_values(array_intersect($available_languages, $languages)); + } + else { + $field_languages[$field_name] = array(FIELD_LANGUAGE_NEUTRAL); + } + } + + return $field_languages[$field_name]; +} + +/** + * Helper function which ensures the the given language code is valid, + * otherwise it returns the current language code. + */ +function _field_valid_language($request_language) { + // TODO: This should use a content language instead of an UI one. + $enabled_languages = array_keys(language_list()); + if (in_array($request_language, $enabled_languages)) { + return $request_language; + } + global $language; + return $language->language; +} + + +/** * Add form elements for all fields for an object to a form structure. * * @param $obj_type @@ -359,13 +437,19 @@ function _field_invoke_multiple_default( * The form structure to fill in. * @param $form_state * An associative array containing the current state of the form. + * @param $language + * TODO: The are two ways to enter field values: predefined language and + * user provided language [...] * * TODO : document the resulting $form structure, like we do for * field_attach_view(). */ -function field_attach_form($obj_type, $object, &$form, &$form_state) { +function field_attach_form($obj_type, $object, &$form, &$form_state, $language = NULL) { + // Ensure that a valid language is used. + $options = array('language' => _field_valid_language($language)); + // TODO : something's not right here : do we alter the form or return a value ? - $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state); + $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state, $options); // Let other modules make changes to the form. foreach (module_implements('field_attach_form') as $module) { @@ -872,17 +956,19 @@ function field_attach_query_revisions($f * @return * A structured content array tree for drupal_render(). */ -function field_attach_view($obj_type, $object, $build_mode = 'full') { +function field_attach_view($obj_type, $object, $build_mode = 'full', $language = NULL) { + // Ensure that a valid language is used. + $options = array('language' => _field_valid_language($language)); + // Let field modules sanitize their data for output. - _field_invoke('sanitize', $obj_type, $object); + _field_invoke('sanitize', $obj_type, $object, $null, $null, $options); - $output = _field_invoke_default('view', $obj_type, $object, $build_mode); + $output = _field_invoke_default('view', $obj_type, $object, $build_mode, $null, $options); // Let other modules make changes after rendering the view. drupal_alter('field_attach_view', $output, $obj_type, $object, $build_mode); return $output; - } /** Index: modules/field/field.crud.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v retrieving revision 1.19 diff -u -p -r1.19 field.crud.inc --- modules/field/field.crud.inc 4 Jul 2009 06:19:46 -0000 1.19 +++ modules/field/field.crud.inc 9 Jul 2009 10:36:52 -0000 @@ -46,6 +46,8 @@ * - cardinality (integer) * The number of values the field can hold. Legal values are any * positive integer or FIELD_CARDINALITY_UNLIMITED. + * - translatable (integer) + * Whether the field is translatable * - locked (integer) * TODO: undefined. * - module (string, read-only) @@ -233,6 +235,7 @@ function field_create_field($field) { $field += array( 'cardinality' => 1, + 'translatable' => FALSE, 'locked' => FALSE, 'settings' => array(), ); Index: modules/field/field.default.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v retrieving revision 1.9 diff -u -p -r1.9 field.default.inc --- modules/field/field.default.inc 24 Jun 2009 18:16:38 -0000 1.9 +++ modules/field/field.default.inc 9 Jul 2009 10:36:52 -0000 @@ -11,21 +11,21 @@ * the corresponding field_attach_[operation]() function. */ -function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) { +function field_default_extract_form_values($obj_type, $object, $field, $instance, $language, &$items, $form, &$form_state) { $field_name = $field['field_name']; - if (isset($form_state['values'][$field_name])) { - $items = $form_state['values'][$field_name]; + if (isset($form_state['values'][$field_name][$language])) { + $items = $form_state['values'][$field_name][$language]; // Remove the 'value' of the 'add more' button. unset($items[$field_name . '_add_more']); } } -function field_default_validate($obj_type, $object, $field, $instance, $items) { +function field_default_validate($obj_type, $object, $field, $instance, $language, $items) { // TODO: here we could validate that required fields are filled in (for programmatic save) } -function field_default_submit($obj_type, $object, $field, $instance, &$items, $form, &$form_state) { +function field_default_submit($obj_type, $object, $field, $instance, $language, &$items, $form, &$form_state) { $field_name = $field['field_name']; // TODO: should me move what's below to __extract_form_values ? @@ -46,7 +46,7 @@ function field_default_submit($obj_type, * This can happen with programmatic saves, or on form-based creation where * the current user doesn't have 'edit' permission for the field. */ -function field_default_insert($obj_type, $object, $field, $instance, &$items) { +function field_default_insert($obj_type, $object, $field, $instance, $language, &$items) { // _field_invoke() populates $items with an empty array if the $object has no // entry for the field, so we check on the $object itself. if (!property_exists($object, $field['field_name']) && !empty($instance['default_value_function'])) { @@ -112,7 +112,7 @@ function field_default_insert($obj_type, * ), * ); */ -function field_default_view($obj_type, $object, $field, $instance, $items, $build_mode) { +function field_default_view($obj_type, $object, $field, $instance, $language, $items, $build_mode) { list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); $addition = array(); @@ -181,7 +181,7 @@ function field_default_view($obj_type, $ return $addition; } -function field_default_prepare_translation($obj_type, $object, $field, $instance, &$items) { +function field_default_prepare_translation($obj_type, $object, $field, $instance, $language, &$items) { $addition = array(); if (isset($object->translation_source->$field['field_name'])) { $addition[$field['field_name']] = $object->translation_source->$field['field_name']; Index: modules/field/field.form.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v retrieving revision 1.10 diff -u -p -r1.10 field.form.inc --- modules/field/field.form.inc 2 Jun 2009 13:47:25 -0000 1.10 +++ modules/field/field.form.inc 9 Jul 2009 15:24:40 -0000 @@ -11,7 +11,7 @@ /** * Create a separate form element for each field. */ -function field_default_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) { +function field_default_form($obj_type, $object, $field, $instance, $language, $items, &$form, &$form_state, $get_delta = NULL) { // This could be called with no object, as when a UI module creates a // dummy form to set default values. if ($object) { @@ -56,7 +56,7 @@ function field_default_form($obj_type, $ // and we are displaying an individual element, process the multiple value // form. if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state); + $form_element = field_multiple_value_form($field, $instance, $language, $items, $form, $form_state); } // If the widget is handling multiple values (e.g Options), // or if we are displaying an individual element, just get a single form @@ -97,7 +97,14 @@ function field_default_form($obj_type, $ '#weight' => $instance['weight'], ); - $addition[$field['field_name']] = array_merge($form_element, $defaults); + $form_element = array_merge($form_element, $defaults); + + $addition[$field['field_name']] = array( + '#tree' => TRUE, + '#weight' => $form_element['#weight'], + $language => $form_element, + ); + $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']); } @@ -112,7 +119,7 @@ function field_default_form($obj_type, $ * - AHAH-'add more' button * - drag-n-drop value reordering */ -function field_multiple_value_form($field, $instance, $items, &$form, &$form_state) { +function field_multiple_value_form($field, $instance, $language, $items, &$form, &$form_state) { $field = field_info_field($instance['field_name']); $field_name = $field['field_name']; @@ -208,9 +215,11 @@ function field_multiple_value_form($fiel '#field_name' => $field_name, '#bundle' => $instance['bundle'], '#attributes' => array('class' => 'field-add-more-submit'), + '#language' => $language, ); } } + return $form_element; } @@ -287,9 +296,9 @@ function theme_field_multiple_value_form /** * Transfer field-level validation errors to widgets. */ -function field_default_form_errors($obj_type, $object, $field, $instance, $items, $form, $errors) { +function field_default_form_errors($obj_type, $object, $field, $instance, $language, $items, $form, $errors) { $field_name = $field['field_name']; - if (!empty($errors[$field_name])) { + if (!empty($errors[$field_name][$language])) { $function = $instance['widget']['module'] . '_field_widget_error'; $function_exists = drupal_function_exists($function); @@ -301,10 +310,10 @@ function field_default_form_errors($obj_ } $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT; - foreach ($errors[$field_name] as $delta => $delta_errors) { + foreach ($errors[$field_name][$language] as $delta => $delta_errors) { // For multiple single-value widgets, pass errors by delta. // For a multiple-value widget, all errors are passed to the main widget. - $error_element = $multiple_widget ? $element : $element[$delta]; + $error_element = $multiple_widget ? $element[$language] : $element[$language][$delta]; foreach ($delta_errors as $error) { if ($function_exists) { $function($error_element, $error); @@ -331,8 +340,9 @@ function field_add_more_submit($form, &$ // Make the changes we want to the form state. $field_name = $form_state['clicked_button']['#field_name']; + $language = $form_state['clicked_button']['#language']; if ($form_state['values'][$field_name . '_add_more']) { - $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]); + $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$language]); } } } @@ -364,7 +374,7 @@ function field_add_more_js($bundle_name, $instance = $form['#fields'][$field_name]['instance']; $form_path = $form['#fields'][$field_name]['form_path']; if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) { - // Ivnalid + // Invalid $invalid = TRUE; } @@ -393,20 +403,26 @@ function field_add_more_js($bundle_name, $form_state['values'] = $form_state_copy['values']; // Reset cached ids, so that they don't affect the actual form we output. drupal_static_reset('form_clean_id'); + + // Ensure that a valid language is provided. + $language = key($_POST[$field_name]); + if ($language != FIELD_LANGUAGE_NEUTRAL) { + $language = _field_valid_language($language); + } // Sort the $form_state['values'] we just built *and* the incoming $_POST data // according to d-n-d reordering. - unset($form_state['values'][$field_name][$field['field_name'] . '_add_more']); - foreach ($_POST[$field_name] as $delta => $item) { - $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight']; + unset($form_state['values'][$field_name][$language][$field['field_name'] . '_add_more']); + foreach ($_POST[$field_name][$language] as $delta => $item) { + $form_state['values'][$field_name][$language][$delta]['_weight'] = $item['_weight']; } - $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]); - $_POST[$field_name] = _field_sort_items($field, $_POST[$field_name]); + $form_state['values'][$field_name][$language] = _field_sort_items($field, $form_state['values'][$field_name][$language]); + $_POST[$field_name][$language] = _field_sort_items($field, $_POST[$field_name][$language]); // Build our new form element for the whole field, asking for one more element. - $form_state['field_item_count'] = array($field_name => count($_POST[$field_name]) + 1); - $items = $form_state['values'][$field_name]; - $form_element = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state); + $form_state['field_item_count'] = array($field_name => count($_POST[$field_name][$language]) + 1); + $items = $form_state['values'][$field_name][$language]; + $form_element = field_default_form(NULL, NULL, $field, $instance, $language, $items, $form, $form_state); // Let other modules alter it. drupal_alter('form', $form_element, array(), 'field_add_more_js'); @@ -423,8 +439,8 @@ function field_add_more_js($bundle_name, // Build the new form against the incoming $_POST values so that we can // render the new element. - $delta = max(array_keys($_POST[$field_name])) + 1; - $_POST[$field_name][$delta]['_weight'] = $delta; + $delta = max(array_keys($_POST[$field_name][$language])) + 1; + $_POST[$field_name][$language][$delta]['_weight'] = $delta; $form_state = form_state_defaults(); $form_state['input'] = $_POST; $form = form_builder($_POST['form_id'], $form, $form_state); Index: modules/field/field.info.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v retrieving revision 1.7 diff -u -p -r1.7 field.info.inc --- modules/field/field.info.inc 30 Jun 2009 03:12:03 -0000 1.7 +++ modules/field/field.info.inc 9 Jul 2009 11:21:16 -0000 @@ -130,6 +130,7 @@ function _field_info_collate_types($rese 'bundle key' => '', 'cacheable' => TRUE, 'bundles' => array(), + 'translatable' => FALSE, ); // If no bundle key provided, then we assume a single bundle, named // after the type of the object. Make sure the bundle created Index: modules/field/field.install =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.install,v retrieving revision 1.9 diff -u -p -r1.9 field.install --- modules/field/field.install 28 May 2009 10:05:32 -0000 1.9 +++ modules/field/field.install 9 Jul 2009 10:36:52 -0000 @@ -63,6 +63,12 @@ function field_schema() { 'not null' => TRUE, 'default' => 0, ), + 'translatable' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), 'active' => array( 'type' => 'int', 'size' => 'tiny', Index: modules/field/field.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.module,v retrieving revision 1.17 diff -u -p -r1.17 field.module --- modules/field/field.module 7 Jul 2009 09:28:07 -0000 1.17 +++ modules/field/field.module 9 Jul 2009 14:12:12 -0000 @@ -64,6 +64,11 @@ require(DRUPAL_ROOT . '/modules/field/fi define('FIELD_CARDINALITY_UNLIMITED', -1); /** + * The language code assigned to untranslatable fields. + */ +define('FIELD_LANGUAGE_NEUTRAL', '_0'); + +/** * TODO */ define('FIELD_BEHAVIOR_NONE', 0x0001); Index: modules/field/field.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.test,v retrieving revision 1.30 diff -u -p -r1.30 field.test --- modules/field/field.test 7 Jul 2009 09:28:07 -0000 1.30 +++ modules/field/field.test 9 Jul 2009 15:20:16 -0000 @@ -59,6 +59,7 @@ class FieldAttachTestCase extends Drupal // field_test_field_load() in field_test.module). $this->instance['settings']['test_hook_field_load'] = TRUE; field_update_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; $entity_type = 'test_entity'; $values = array(); @@ -73,12 +74,12 @@ class FieldAttachTestCase extends Drupal $current_revision = $revision_id; // If this is the first revision do an insert. if (!$revision_id) { - $revision[$revision_id]->{$this->field_name} = $values[$revision_id]; + $revision[$revision_id]->{$this->field_name}[$language] = $values[$revision_id]; field_attach_insert($entity_type, $revision[$revision_id]); } else { // Otherwise do an update. - $revision[$revision_id]->{$this->field_name} = $values[$revision_id]; + $revision[$revision_id]->{$this->field_name}[$language] = $values[$revision_id]; field_attach_update($entity_type, $revision[$revision_id]); } } @@ -87,12 +88,12 @@ class FieldAttachTestCase extends Drupal $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], t('Currrent revision: expected number of values')); + $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], t('Currrent revision: expected number of values')); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$delta]['value'] , $values[$current_revision][$delta]['value'], t('Currrent revision: expected value %delta was found.', array('%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Currrent revision: expected value %delta was found.', array('%delta' => $delta))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Currrent revision: extra information for value %delta was found', array('%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['additional_key'], 'additional_value', t('Currrent revision: extra information for value %delta was found', array('%delta' => $delta))); } // Confirm each revision loads the correct data. @@ -100,12 +101,12 @@ class FieldAttachTestCase extends Drupal $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); field_attach_load_revision($entity_type, array(0 => $entity)); // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); + $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); } } } @@ -115,6 +116,7 @@ class FieldAttachTestCase extends Drupal */ function testFieldAttachLoadMultiple() { $entity_type = 'test_entity'; + $language = FIELD_LANGUAGE_NEUTRAL; // Define 2 bundles. $bundles = array( @@ -157,7 +159,7 @@ class FieldAttachTestCase extends Drupal $instances = field_info_instances($bundle); foreach ($instances as $field_name => $instance) { $values[$index][$field_name] = mt_rand(1, 127); - $entity->$field_name = array(array('value' => $values[$index][$field_name])); + $entity->$field_name = array($language => array(array('value' => $values[$index][$field_name]))); } field_attach_insert($entity_type, $entity); } @@ -168,9 +170,9 @@ class FieldAttachTestCase extends Drupal $instances = field_info_instances($bundles[$index]); foreach ($instances as $field_name => $instance) { // The field value loaded matches the one inserted. - $this->assertEqual($entity->{$field_name}[0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); + $this->assertEqual($entity->{$field_name}[$language][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$field_name}[0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); + $this->assertEqual($entity->{$field_name}[$language][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); } } } @@ -181,6 +183,7 @@ class FieldAttachTestCase extends Drupal function testFieldAttachSaveMissingData() { $entity_type = 'test_entity'; $entity_init = field_test_create_stub_entity(); + $language = FIELD_LANGUAGE_NEUTRAL; // Insert: Field is missing. $entity = clone($entity_init); @@ -188,28 +191,28 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: missing field results in no value saved')); // Insert: Field is NULL. field_cache_clear(); $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; + $entity->{$this->field_name}[$language] = NULL; field_attach_insert($entity_type, $entity); $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: NULL field results in no value saved')); // Add some real data. field_cache_clear(); $entity = clone($entity_init); $values = $this->_generateTestFieldValues(1); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; field_attach_insert($entity_type, $entity); $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}, $values, t('Field data saved')); + $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Field data saved')); // Update: Field is missing. Data should survive. field_cache_clear(); @@ -218,17 +221,17 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}, $values, t('Update: missing field leaves existing values in place')); + $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Update: missing field leaves existing values in place')); // Update: Field is NULL. Data should be wiped. field_cache_clear(); $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; + $entity->{$this->field_name}[$language] = NULL; field_attach_update($entity_type, $entity); $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values')); + $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Update: NULL field removes existing values')); } /** @@ -241,15 +244,16 @@ class FieldAttachTestCase extends Drupal $entity_type = 'test_entity'; $entity_init = field_test_create_stub_entity(); + $language = FIELD_LANGUAGE_NEUTRAL; // Insert: Field is NULL. $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; + $entity->{$this->field_name}[$language] = NULL; field_attach_insert($entity_type, $entity); $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); + $this->assertTrue(empty($entity->{$this->field_name}[$language]), t('Insert: NULL field results in no value saved')); // Insert: Field is missing. field_cache_clear(); @@ -259,7 +263,7 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($entity_type, array($entity->ftid => $entity)); $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance); - $this->assertEqual($entity->{$this->field_name}, $values, t('Insert: missing field results in default value saved')); + $this->assertEqual($entity->{$this->field_name}[$language], $values, t('Insert: missing field results in default value saved')); } /** @@ -267,6 +271,7 @@ class FieldAttachTestCase extends Drupal */ function testFieldAttachQuery() { $cardinality = $this->field['cardinality']; + $language = FIELD_LANGUAGE_NEUTRAL; // Create an additional bundle with an instance of the field. field_test_create_bundle('test_bundle_1', 'Test Bundle 1'); @@ -285,13 +290,13 @@ class FieldAttachTestCase extends Drupal $value = mt_rand(1, 127); } while (in_array($value, $values)); $values[$delta] = $value; - $entities[1]->{$this->field_name}[$delta] = array('value' => $values[$delta]); + $entities[1]->{$this->field_name}[$language][$delta] = array('value' => $values[$delta]); } field_attach_insert($entity_types[1], $entities[1]); // Create second test object, sharing a value with the first one. $common_value = $values[$cardinality - 1]; - $entities[2]->{$this->field_name} = array(array('value' => $common_value)); + $entities[2]->{$this->field_name} = array($language => array(array('value' => $common_value))); field_attach_insert($entity_types[2], $entities[2]); // Query on the object's values. @@ -348,19 +353,20 @@ class FieldAttachTestCase extends Drupal // Create first object revision with random (distinct) values. $entity_type = 'test_entity'; $entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2)); + $language = FIELD_LANGUAGE_NEUTRAL; $values = array(); for ($delta = 0; $delta < $cardinality; $delta++) { do { $value = mt_rand(1, 127); } while (in_array($value, $values)); $values[$delta] = $value; - $entities[1]->{$this->field_name}[$delta] = array('value' => $values[$delta]); + $entities[1]->{$this->field_name}[$language][$delta] = array('value' => $values[$delta]); } field_attach_insert($entity_type, $entities[1]); // Create second object revision, sharing a value with the first one. $common_value = $values[$cardinality - 1]; - $entities[2]->{$this->field_name}[0] = array('value' => $common_value); + $entities[2]->{$this->field_name}[$language][0] = array('value' => $common_value); field_attach_update($entity_type, $entities[2]); // Query on the object's values. @@ -403,10 +409,11 @@ class FieldAttachTestCase extends Drupal function testFieldAttachViewAndPreprocess() { $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; // Populate values to be displayed. $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; // Simple formatter, label displayed. $formatter_setting = $this->randomName(); @@ -480,28 +487,29 @@ class FieldAttachTestCase extends Drupal function testFieldAttachDelete() { $entity_type = 'test_entity'; + $language = FIELD_LANGUAGE_NEUTRAL; $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); // Create revision 0 $values = $this->_generateTestFieldValues($this->field['cardinality']); - $rev[0]->{$this->field_name} = $values; + $rev[0]->{$this->field_name}[$language] = $values; field_attach_insert($entity_type, $rev[0]); // Create revision 1 $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']); - $rev[1]->{$this->field_name} = $values; + $rev[1]->{$this->field_name}[$language] = $values; field_attach_update($entity_type, $rev[1]); // Create revision 2 $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - $rev[2]->{$this->field_name} = $values; + $rev[2]->{$this->field_name}[$language] = $values; field_attach_update($entity_type, $rev[2]); // Confirm each revision loads foreach (array_keys($rev) as $vid) { $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); + $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); } // Delete revision 1, confirm the other two still load. @@ -509,13 +517,13 @@ class FieldAttachTestCase extends Drupal foreach (array(0, 2) as $vid) { $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); + $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values."); } // Confirm the current revision still loads $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values."); + $this->assertEqual(count($read->{$this->field_name}[$language]), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values."); // Delete all field data, confirm nothing loads field_attach_delete($entity_type, $rev[2]); @@ -541,15 +549,16 @@ class FieldAttachTestCase extends Drupal // Save an object with data in the field. $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; $entity_type = 'test_entity'; field_attach_insert($entity_type, $entity); // Verify the field data is present on load. $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle"); + $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], "Data are retrieved for the new bundle"); // Rename the bundle. This has to be initiated by the module so that its // hook_fieldable_info() is consistent. @@ -563,7 +572,7 @@ class FieldAttachTestCase extends Drupal // Verify the field data is present on load. $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage"); + $this->assertEqual(count($entity->{$this->field_name}[$language]), $this->field['cardinality'], "Bundle name has been updated in the field storage"); } function testFieldAttachDeleteBundle() { @@ -595,17 +604,18 @@ class FieldAttachTestCase extends Drupal // Save an object with data for both fields $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name} = $values; - $entity->{$field_name} = $this->_generateTestFieldValues(1); + $entity->{$this->field_name}[$language] = $values; + $entity->{$field_name}[$language] = $this->_generateTestFieldValues(1); $entity_type = 'test_entity'; field_attach_insert($entity_type, $entity); // Verify the fields are present on load $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded"); - $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded"); + $this->assertEqual(count($entity->{$this->field_name}[$language]), 4, "First field got loaded"); + $this->assertEqual(count($entity->{$field_name}[$language]), 1, "Second field got loaded"); // Delete the bundle. This has to be initiated by the module so that its // hook_fieldable_info() is consistent. @@ -614,8 +624,8 @@ class FieldAttachTestCase extends Drupal // Verify no data gets loaded $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); field_attach_load($entity_type, array(0 => $entity)); - $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field"); - $this->assertFalse(isset($entity->{$field_name}), "No data for second field"); + $this->assertFalse(isset($entity->{$this->field_name}[$language]), "No data for first field"); + $this->assertFalse(isset($entity->{$field_name}[$language]), "No data for second field"); // Verify that the instances are gone $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted"); @@ -628,6 +638,7 @@ class FieldAttachTestCase extends Drupal function testFieldAttachCache() { // Initialize random values and a test entity. $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; $values = $this->_generateTestFieldValues($this->field['cardinality']); $noncached_type = 'test_entity'; @@ -642,7 +653,7 @@ class FieldAttachTestCase extends Drupal // Save, and check that no cache entry is present. $entity = clone($entity_init); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; field_attach_insert($noncached_type, $entity); $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert')); @@ -660,7 +671,7 @@ class FieldAttachTestCase extends Drupal // Save, and check that no cache entry is present. $entity = clone($entity_init); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; field_attach_insert($cached_type, $entity); $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert')); @@ -668,12 +679,12 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($cached_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load')); // Update with different values, and check that the cache entry is wiped. $values = $this->_generateTestFieldValues($this->field['cardinality']); $entity = clone($entity_init); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; field_attach_update($cached_type, $entity); $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update')); @@ -681,13 +692,13 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($cached_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load')); // Create a new revision, and check that the cache entry is wiped. $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']); $values = $this->_generateTestFieldValues($this->field['cardinality']); $entity = clone($entity_init); - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; field_attach_update($cached_type, $entity); $cache = cache_get($cid, 'cache_field'); $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation')); @@ -696,7 +707,7 @@ class FieldAttachTestCase extends Drupal $entity = clone($entity_init); field_attach_load($cached_type, array($entity->ftid => $entity)); $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name], $values, t('Cached: correct cache entry on load')); + $this->assertEqual($cache->data[$this->field_name][$language], $values, t('Cached: correct cache entry on load')); // Delete, and check that the cache entry is wiped. field_attach_delete($cached_type, $entity); @@ -708,6 +719,7 @@ class FieldAttachTestCase extends Drupal function testFieldAttachValidate() { $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; // Set up values to generate errors $values = array(); @@ -717,7 +729,7 @@ class FieldAttachTestCase extends Drupal } // Arrange for item 1 not to generate an error $values[1]['value'] = 1; - $entity->{$this->field_name} = $values; + $entity->{$this->field_name}[$language] = $values; try { field_attach_validate($entity_type, $entity); @@ -728,15 +740,15 @@ class FieldAttachTestCase extends Drupal foreach ($values as $delta => $value) { if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$delta]), 1, "Only one error set on value $delta"); - unset($errors[$this->field_name][$delta]); + $this->assertIdentical($errors[$this->field_name][$language][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); + $this->assertEqual(count($errors[$this->field_name][$language][$delta]), 1, "Only one error set on value $delta"); + unset($errors[$this->field_name][$language][$delta]); } else { - $this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta"); + $this->assertFalse(isset($errors[$this->field_name][$language][$delta]), "No error set on value $delta"); } } - $this->assertEqual(count($errors[$this->field_name]), 0, 'No extraneous errors set'); + $this->assertEqual(count($errors[$this->field_name][$language]), 0, 'No extraneous errors set'); } // Validate that FAPI elements are generated. This could be much @@ -748,10 +760,11 @@ class FieldAttachTestCase extends Drupal $form = $form_state = array(); field_attach_form($entity_type, $entity, $form, $form_state); - $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); + $language = FIELD_LANGUAGE_NEUTRAL; + $this->assertEqual($form[$this->field_name][$language]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // field_test_widget uses 'textfield' - $this->assertEqual($form[$this->field_name][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); + $this->assertEqual($form[$this->field_name][$language][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); } } @@ -778,7 +791,8 @@ class FieldAttachTestCase extends Drupal // Leave an empty value. 'field_test' fields are empty if empty(). $values[1]['value'] = 0; - $form_state['values'] = array($this->field_name => $values); + $language = FIELD_LANGUAGE_NEUTRAL; + $form_state['values'] = array($this->field_name => array($language => $values)); field_attach_submit($entity_type, $entity, $form, $form_state); asort($weights); @@ -788,7 +802,7 @@ class FieldAttachTestCase extends Drupal $expected_values[] = array('value' => $values[$key]['value']); } } - $this->assertIdentical($entity->{$this->field_name}, $expected_values, 'Submit filters empty values'); + $this->assertIdentical($entity->{$this->field_name}[$language], $expected_values, 'Submit filters empty values'); } /** @@ -958,45 +972,46 @@ class FieldFormTestCase extends DrupalWe $this->instance['field_name'] = $this->field_name; field_create_field($this->field); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget is displayed'); - $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget is displayed'); + $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed'); // TODO : check that the widget is populated with default value ? // Submit with invalid value (field-level validation). - $edit = array($this->field_name . '[0][value]' => -1); + $edit = array($this->field_name . '[' . $language . '][0][value]' => -1); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.'); // TODO : check that the correct field is flagged for error. // Create an entity $value = mt_rand(1, 127); - $edit = array($this->field_name . '[0][value]' => $value); + $edit = array($this->field_name . '[' . $language . '][0][value]' => $value); $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); $entity = field_test_entity_load($id); - $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved'); + $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was saved'); // Display edit form. $this->drupalGet('test-entity/' . $id . '/edit'); - $this->assertFieldByName($this->field_name . '[0][value]', $value, 'Widget is displayed with the correct default value'); - $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', $value, 'Widget is displayed with the correct default value'); + $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed'); // Update the entity. $value = mt_rand(1, 127); - $edit = array($this->field_name . '[0][value]' => $value); + $edit = array($this->field_name . '[' . $language . '][0][value]' => $value); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); $entity = field_test_entity_load($id); - $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was updated'); + $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was updated'); // Empty the field. $value = ''; - $edit = array($this->field_name . '[0][value]' => $value); + $edit = array($this->field_name . '[' . $language . '][0][value]' => $value); $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); $entity = field_test_entity_load($id); @@ -1011,6 +1026,7 @@ class FieldFormTestCase extends DrupalWe $this->instance['required'] = TRUE; field_create_field($this->field); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Submit with missing required value. $edit = array(); @@ -1019,17 +1035,17 @@ class FieldFormTestCase extends DrupalWe // Create an entity $value = mt_rand(1, 127); - $edit = array($this->field_name . '[0][value]' => $value); + $edit = array($this->field_name . '[' . $language . '][0][value]' => $value); $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/(\d+)/edit|', $this->url, $match); $id = $match[1]; $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); $entity = field_test_entity_load($id); - $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field value was saved'); + $this->assertEqual($entity->{$this->field_name}[$language][0]['value'], $value, 'Field value was saved'); // Edit with missing required value. $value = ''; - $edit = array($this->field_name . '[0][value]' => $value); + $edit = array($this->field_name . '[' . $language . '][0][value]' => $value); $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); } @@ -1048,17 +1064,18 @@ class FieldFormTestCase extends DrupalWe $this->instance['field_name'] = $this->field_name; field_create_field($this->field); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Display creation form -> 1 widget. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed'); - $this->assertNoField($this->field_name . '[1][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget 1 is displayed'); + $this->assertNoField($this->field_name . '[' . $language . '][1][value]', 'No extraneous widget is displayed'); // Press 'add more' button -> 2 widgets. $this->drupalPost(NULL, array(), t('Add another item')); - $this->assertFieldByName($this->field_name . '[0][value]', '', 'Widget 1 is displayed'); - $this->assertFieldByName($this->field_name . '[1][value]', '', 'New widget is displayed'); - $this->assertNoField($this->field_name . '[2][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', 'Widget 1 is displayed'); + $this->assertFieldByName($this->field_name . '[' . $language . '][1][value]', '', 'New widget is displayed'); + $this->assertNoField($this->field_name . '[' . $language . '][2][value]', 'No extraneous widget is displayed'); // TODO : check that non-field inpurs are preserved ('title')... // Yet another time so that we can play with more values -> 3 widgets. @@ -1075,8 +1092,8 @@ class FieldFormTestCase extends DrupalWe } while (in_array($weight, $weights)); $weights[] = $weight; $value = mt_rand(1, 127); - $edit["$this->field_name[$delta][value]"] = $value; - $edit["$this->field_name[$delta][_weight]"] = $weight; + $edit["$this->field_name[$language][$delta][value]"] = $value; + $edit["$this->field_name[$language][$delta][_weight]"] = $weight; // We'll need three slightly different formats to check the values. $values[$weight] = $value; $field_values[$weight]['value'] = (string)$value; @@ -1088,15 +1105,15 @@ class FieldFormTestCase extends DrupalWe ksort($values); $values = array_values($values); for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight"); + $this->assertFieldByName("$this->field_name[$language][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); + $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "Widget $delta has the right weight"); } ksort($pattern); $pattern = implode('.*', array_values($pattern)); $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); - $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName("$this->field_name[$language][$delta][value]", '', "New widget is displayed"); + $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "New widget has the right weight"); + $this->assertNoField("$this->field_name[$language][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); // Submit the form and create the entity. $this->drupalPost(NULL, $edit, t('Save')); @@ -1106,7 +1123,7 @@ class FieldFormTestCase extends DrupalWe $entity = field_test_entity_load($id); ksort($field_values); $field_values = array_values($field_values); - $this->assertIdentical($entity->{$this->field_name}, $field_values, 'Field values were saved in the correct order'); + $this->assertIdentical($entity->{$this->field_name}[$language], $field_values, 'Field values were saved in the correct order'); // Display edit form: check that the expected number of widgets is // displayed, with correct values change values, reorder, leave an empty @@ -1128,6 +1145,7 @@ class FieldFormTestCase extends DrupalWe $this->instance['field_name'] = $this->field_name; field_create_field($this->field); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Display creation form -> 1 widget. $this->drupalGet('test-entity/add/test-bundle'); @@ -1149,8 +1167,8 @@ class FieldFormTestCase extends DrupalWe } while (in_array($weight, $weights)); $weights[] = $weight; $value = mt_rand(1, 127); - $edit["$this->field_name[$delta][value]"] = $value; - $edit["$this->field_name[$delta][_weight]"] = $weight; + $edit["$this->field_name[$language][$delta][value]"] = $value; + $edit["$this->field_name[$language][$delta][_weight]"] = $weight; // We'll need three slightly different formats to check the values. $values[$weight] = $value; $field_values[$weight]['value'] = (string)$value; @@ -1163,15 +1181,15 @@ class FieldFormTestCase extends DrupalWe ksort($values); $values = array_values($values); for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "Widget $delta has the right weight"); + $this->assertFieldByName("$this->field_name[$language][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); + $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "Widget $delta has the right weight"); } ksort($pattern); $pattern = implode('.*', array_values($pattern)); $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); - $this->assertFieldByName("$this->field_name[$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); + $this->assertFieldByName("$this->field_name[$language][$delta][value]", '', "New widget is displayed"); + $this->assertFieldByName("$this->field_name[$language][$delta][_weight]", $delta, "New widget has the right weight"); + $this->assertNoField("$this->field_name[$language][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); } /** @@ -1406,17 +1424,18 @@ class FieldCrudTestCase extends DrupalWe // Save an object with data for the field $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; $values[0]['value'] = mt_rand(1, 127); - $entity->{$field['field_name']} = $values; + $entity->{$field['field_name']}[$language] = $values; $entity_type = 'test_entity'; field_attach_insert($entity_type, $entity); // Verify the field is present on load $entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']); field_attach_load($entity_type, array(0 => $entity)); - $this->assertIdentical(count($entity->{$field['field_name']}), count($values), "Data in previously deleted field saves and loads correctly"); + $this->assertIdentical(count($entity->{$field['field_name']}[$language]), count($values), "Data in previously deleted field saves and loads correctly"); foreach ($values as $delta => $value) { - $this->assertEqual($entity->{$field['field_name']}[$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly"); + $this->assertEqual($entity->{$field['field_name']}[$language][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly"); } } } @@ -1585,3 +1604,194 @@ class FieldInstanceTestCase extends Drup $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.')); } } + +/** + * Unit test class for the multilanguage fields logic. + * + * The following tests will check the multilanguage logic of + * _field_invoke and that only the correct values are returned + * by _field_available_languages. + */ +class FieldTranslationsTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => t('Field translations tests'), + 'description' => t("Test multilanguage fields logic."), + 'group' => t('Field') + ); + } + + function setUp() { + parent::setUp('locale', 'field_test'); + + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + + $this->obj_type = 'test_entity'; + + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'test_field', + 'cardinality' => 4, + 'translatable' => TRUE, + 'settings' => array( + 'test_hook_in' => FALSE, + ), + ); + field_create_field($this->field); + + $this->instance = array( + 'field_name' => $this->field_name, + 'bundle' => 'test_bundle', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + field_create_instance($this->instance); + + require_once DRUPAL_ROOT . '/includes/locale.inc'; + for ($i = 0; $i < 3; ++$i) { + locale_add_language('l'.$i, $this->randomString(), $this->randomString()); + } + } + + /** + * Check that that only the correct values are returned by _field_available_languages. + */ + function testFieldAvailableLanguages() { + // Test translatable fieldable info. + $field = $this->field; + $field['field_name'] .= '_untranslatable'; + $language = language_default(); + $suggested_languages = array($language->language); + $available_languages = _field_available_languages($this->obj_type, $field); + $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NEUTRAL, t('Untranslatable entity: suggested language ignored')); + + // Enable field translations for the entity. + _field_info_collate_types(TRUE); + field_test_fieldable_info_translatable(TRUE); + + // Test hook_field_languages invocation on a translatable field. + $this->field['settings']['test_hook_in'] = TRUE; + $enabled_languages = array_keys(language_list()); + $available_languages = _field_available_languages($this->obj_type, $this->field); + foreach ($available_languages as $language) { + $this->assertTrue(in_array($language, $enabled_languages), t('%language is an enabled language', array('%language' => $language))); + } + $this->assertFalse(in_array('xx', $available_languages), t('No invalid language was made available')); + $this->assertTrue(count($available_languages) == count($enabled_languages) - 1, t('An enabled language was successfully made unavailable')); + + // Test _field_available_languages behavior for untranslatable fields. + $this->field['translatable'] = FALSE; + $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); + $available_languages = _field_available_languages($this->obj_type, $this->field); + $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NEUTRAL, t('For untranslatable fields only neutral language is available')); + + // Test language suggestions. + $this->field['settings']['test_hook_in'] = FALSE; + $this->field['translatable'] = TRUE; + $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); + $suggested_languages = array(); + $lang_count = mt_rand(1, count($enabled_languages) - 1); + for ($i = 0; $i < $lang_count; ++$i) { + do { + $language = $enabled_languages[mt_rand(0, $lang_count)]; + } + while (in_array($language, $suggested_languages)); + $suggested_languages[] = $language; + } + $available_languages = _field_available_languages($this->obj_type, $this->field, $suggested_languages); + $this->assertEqual(count($available_languages), count($suggested_languages), t('Suggested languages were succesully made available')); + foreach ($available_languages as $language) { + $this->assertTrue(in_array($language, $available_languages), t('Suggested language %language is available', array('%language' => $language))); + } + $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); + $suggested_languages = array('xx'); + $available_languages = _field_available_languages($this->obj_type, $this->field, $suggested_languages); + $this->assertTrue(empty($available_languages), t('An invalid suggested language was not made available')); + } + + /** + * Test the multilanguage logic of _field_invoke. + */ + function testFieldInvoke() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + // Populate some extra languages to check if _field_invoke correctly uses + // the result of _field_available_languages. + $values = array(); + $extra_languages = mt_rand(1, 4); + $languages = $available_languages = _field_available_languages($this->obj_type, $this->field); + for ($i = 0; $i < $extra_languages; ++$i) { + $languages[] = $this->randomString(2); + } + + // For each given language provide some random values. + foreach ($languages as $language) { + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$language][$delta]['value'] = mt_rand(1, 127); + } + } + $entity->{$this->field_name} = $values; + + $results = _field_invoke('test_op', $entity_type, $entity); + foreach ($results as $language => $result) { + $hash = md5(serialize(array($entity_type, $entity, $this->field_name, $language, $values[$language]))); + // Check if the parameters passed to _field_invoke were correctly forwarded to the callback function. + $this->assertEqual($hash, $result, t('The result for %language is correctly stored', array('%language' => $language))); + } + $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed')); + } + + /** + * Test the multilanguage logic of _field_invoke_multiple. + */ + function testFieldInvokeMultiple() { + $values = array(); + $entities = array(); + $entity_type = 'test_entity'; + $entity_count = mt_rand(1, 5); + $available_languages = _field_available_languages($this->obj_type, $this->field); + + for ($id = 1; $id <= $entity_count; ++$id) { + $entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']); + $languages = $available_languages; + + // Populate some extra languages to check if _field_invoke correctly uses + // the result of _field_available_languages. + $extra_languages = mt_rand(1, 4); + for ($i = 0; $i < $extra_languages; ++$i) { + $languages[] = $this->randomString(2); + } + + // For each given language provide some random values. + foreach ($languages as $language) { + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$id][$language][$delta]['value'] = mt_rand(1, 127); + } + } + $entity->{$this->field_name} = $values[$id]; + $entities[$id] = $entity; + } + + $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities); + foreach ($grouped_results as $id => $results) { + foreach ($results as $language => $result) { + $hash = md5(serialize(array($entity_type, $entities[$id], $this->field_name, $language, $values[$id][$language]))); + // Check if the parameters passed to _field_invoke were correctly forwarded to the callback function. + $this->assertEqual($hash, $result, t('The result for object %id/%language is correctly stored', array('%id' => $id, '%language' => $language))); + } + $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for object %id', array('%id' => $id))); + } + } +} Index: modules/field/modules/field_sql_storage/field_sql_storage.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v retrieving revision 1.15 diff -u -p -r1.15 field_sql_storage.module --- modules/field/modules/field_sql_storage/field_sql_storage.module 7 Jul 2009 09:28:07 -0000 1.15 +++ modules/field/modules/field_sql_storage/field_sql_storage.module 9 Jul 2009 11:26:52 -0000 @@ -144,8 +144,16 @@ function _field_sql_storage_schema($fiel 'not null' => TRUE, 'description' => 'The sequence number for this data item, used for multi-value fields', ), + // @todo Consider an integer field for 'language'. + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language for this data item.', + ), ), - 'primary key' => array('etid', 'entity_id', 'deleted', 'delta'), + 'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'), // TODO : index on 'bundle' ); @@ -169,7 +177,7 @@ function _field_sql_storage_schema($fiel $revision = $current; $revision['description'] = "Revision archive storage for {$deleted}field {$field['id']} ({$field['field_name']})"; $revision['revision_id']['description'] = 'The entity revision id this data is attached to'; - $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta'); + $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta', 'language'); return array( _field_sql_storage_tablename($field) => $current, @@ -215,7 +223,7 @@ function field_sql_storage_field_storage if (!isset($skip_fields[$field_name])) { $objects[$id]->{$field_name} = array(); $field_ids[$field_name][] = $load_current ? $id : $vid; - $delta_count[$id][$field_name] = 0; + $delta_count[$id][$field_name] = array(); } } } @@ -229,11 +237,16 @@ function field_sql_storage_field_storage ->condition('etid', $etid) ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN') ->condition('deleted', 0) + ->condition('language', _field_available_languages($obj_type, $field)) ->orderBy('delta') ->execute(); foreach ($results as $row) { - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { + if (!isset($delta_count[$row->entity_id][$field_name][$row->language])) { + $delta_count[$row->entity_id][$field_name][$row->language] = 0; + } + + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name][$row->language] < $field['cardinality']) { $item = array(); // For each column declared by the field, populate the item // from the prefixed database column. @@ -243,8 +256,8 @@ function field_sql_storage_field_storage } // Add the item to the field values for the entity. - $objects[$row->entity_id]->{$field_name}[] = $item; - $delta_count[$row->entity_id][$field_name]++; + $objects[$row->entity_id]->{$field_name}[$row->language][] = $item; + $delta_count[$row->entity_id][$field_name][$row->language]++; } } } @@ -274,17 +287,33 @@ function field_sql_storage_field_storage // Function property_exists() is slower, so we catch the more frequent cases // where it's an empty array with the faster isset(). if (isset($object->$field_name) || property_exists($object, $field_name)) { + $available_languages = _field_available_languages($obj_type, $field); + $available_translations = is_array($object->$field_name) ? array_intersect($available_languages, array_keys($object->$field_name)) : FALSE; + // Delete and insert, rather than update, in case a value was added. - if ($op == FIELD_STORAGE_UPDATE) { - db_delete($table_name)->condition('etid', $etid)->condition('entity_id', $id)->execute(); + // If no translation is available, empty the field for all the available languages. + if ($op == FIELD_STORAGE_UPDATE && count($available_translations)) { + $languages = empty($object->$field_name) ? $available_languages : $available_translations; + + db_delete($table_name) + ->condition('etid', $etid) + ->condition('entity_id', $id) + ->condition('language', $languages) + ->execute(); + if (isset($vid)) { - db_delete($revision_name)->condition('etid', $etid)->condition('entity_id', $id)->condition('revision_id', $vid)->execute(); + db_delete($revision_name) + ->condition('etid', $etid) + ->condition('entity_id', $id) + ->condition('revision_id', $vid) + ->condition('language', $languages) + ->execute(); } } - if ($object->$field_name) { - // Prepare the multi-insert query. - $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta'); + if (!empty($available_translations)) { + // Prepare the multi-insert query. + $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); foreach ($field['columns'] as $column => $attributes) { $columns[] = _field_sql_storage_columnname($field_name, $column); } @@ -293,25 +322,30 @@ function field_sql_storage_field_storage $revision_query = db_insert($revision_name)->fields($columns); } - $delta_count = 0; - foreach ($object->$field_name as $delta => $item) { - $record = array( - 'etid' => $etid, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - ); - foreach ($field['columns'] as $column => $attributes) { - $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; - } - $query->values($record); - if (isset($vid)) { - $revision_query->values($record); - } - - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { - break; + foreach ($available_translations as $language) { + if ($items = $object->{$field_name}[$language]) { + $delta_count = 0; + foreach ($items as $delta => $item) { + $record = array( + 'etid' => $etid, + 'entity_id' => $id, + 'revision_id' => $vid, + 'bundle' => $bundle, + 'delta' => $delta, + 'language' => $language, + ); + foreach ($field['columns'] as $column => $attributes) { + $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; + } + $query->values($record); + if (isset($vid)) { + $revision_query->values($record); + } + + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { + break; + } + } } } Index: modules/field/modules/field_sql_storage/field_sql_storage.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test,v retrieving revision 1.4 diff -u -p -r1.4 field_sql_storage.test --- modules/field/modules/field_sql_storage/field_sql_storage.test 28 May 2009 10:05:32 -0000 1.4 +++ modules/field/modules/field_sql_storage/field_sql_storage.test 9 Jul 2009 10:36:52 -0000 @@ -56,9 +56,10 @@ class FieldSqlStorageTestCase extends Dr function testFieldAttachLoad() { $entity_type = 'test_entity'; $eid = 0; + $language = FIELD_LANGUAGE_NEUTRAL; $etid = _field_sql_storage_etid($entity_type); - $columns = array('etid', 'entity_id', 'revision_id', 'delta', $this->field_name . '_value'); + $columns = array('etid', 'entity_id', 'revision_id', 'delta', 'language', $this->field_name . '_value'); // Insert data for four revisions to the field revisions table $query = db_insert($this->revision_table)->fields($columns); @@ -68,7 +69,7 @@ class FieldSqlStorageTestCase extends Dr for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { $value = mt_rand(1, 127); $values[$evid][] = $value; - $query->values(array($etid, $eid, $evid, $delta, $value)); + $query->values(array($etid, $eid, $evid, $delta, $language, $value)); } } $query->execute(); @@ -76,7 +77,7 @@ class FieldSqlStorageTestCase extends Dr // Insert data for the "most current revision" into the field table $query = db_insert($this->table)->fields($columns); foreach ($values[0] as $delta => $value) { - $query->values(array($etid, $eid, 0, $delta, $value)); + $query->values(array($etid, $eid, 0, $delta, $language, $value)); } $query->execute(); @@ -85,10 +86,10 @@ class FieldSqlStorageTestCase extends Dr field_attach_load($entity_type, array($eid => $entity)); foreach ($values[0] as $delta => $value) { if ($delta < $this->field['cardinality']) { - $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $value, "Value $delta is loaded correctly for current revision"); } else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision."); + $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$language]), "No extraneous value gets loaded for current revision."); } } @@ -98,13 +99,23 @@ class FieldSqlStorageTestCase extends Dr field_attach_load_revision($entity_type, array($eid => $entity)); foreach ($values[$evid] as $delta => $value) { if ($delta < $this->field['cardinality']) { - $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); + $this->assertEqual($entity->{$this->field_name}[$language][$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly"); } else { - $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid."); + $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}[$language]), "No extraneous value gets loaded for revision $evid."); } } } + + // Add a translation in an unavailable language and verify it is not loaded. + $eid = $evid = 1; + $unavailable_language = 'xx'; + $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); + $values = array($etid, $eid, $evid, 0, $unavailable_language, mt_rand(1, 127)); + db_insert($this->table)->fields($columns)->values($values)->execute(); + db_insert($this->revision_table)->fields($columns)->values($values)->execute(); + field_attach_load($entity_type, array($eid => $entity)); + $this->assertFalse(array_key_exists($unavailable_language, $entity->{$this->field_name}), 'Field translation in an unavailable language ignored'); } /** @@ -114,6 +125,7 @@ class FieldSqlStorageTestCase extends Dr function testFieldAttachInsertAndUpdate() { $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; // Test insert. $values = array(); @@ -122,7 +134,7 @@ class FieldSqlStorageTestCase extends Dr for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { $values[$delta]['value'] = mt_rand(1, 127); } - $entity->{$this->field_name} = $rev_values[0] = $values; + $entity->{$this->field_name}[$language] = $rev_values[0] = $values; field_attach_insert($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); @@ -142,7 +154,7 @@ class FieldSqlStorageTestCase extends Dr for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) { $values[$delta]['value'] = mt_rand(1, 127); } - $entity->{$this->field_name} = $rev_values[1] = $values; + $entity->{$this->field_name}[$language] = $rev_values[1] = $values; field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { @@ -170,9 +182,9 @@ class FieldSqlStorageTestCase extends Dr } $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}"); - // Check that update leaves the field data untouched if $object has no - // $field_name key. - unset($entity->{$this->field_name}); + // Check that update leaves the field data untouched if $object->{$field_name} has no + // language key. + unset($entity->{$this->field_name}[$language]); field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { @@ -182,7 +194,7 @@ class FieldSqlStorageTestCase extends Dr } // Check that update with an empty $object->$field_name empties the field. - $entity->{$this->field_name} = NULL; + $entity->{$this->field_name}[$language] = NULL; field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); @@ -194,20 +206,21 @@ class FieldSqlStorageTestCase extends Dr function testFieldAttachSaveMissingData() { $entity_type = 'test_entity'; $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $language = FIELD_LANGUAGE_NEUTRAL; - // Insert: Field is missing + // Insert: Field is missing. field_attach_insert($entity_type, $entity); $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); $this->assertEqual($count, 0, 'Missing field results in no inserts'); - // Insert: Field is NULL - $entity->{$this->field_name} = NULL; + // Insert: Field is NULL. + $entity->{$this->field_name}[$language] = NULL; field_attach_insert($entity_type, $entity); $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); $this->assertEqual($count, 0, 'NULL field results in no inserts'); - // Add some real data - $entity->{$this->field_name} = array(0 => array('value' => 1)); + // Add some real data. + $entity->{$this->field_name}[$language] = array(0 => array('value' => 1)); field_attach_insert($entity_type, $entity); $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); $this->assertEqual($count, 1, 'Field data saved'); @@ -223,5 +236,33 @@ class FieldSqlStorageTestCase extends Dr field_attach_update($entity_type, $entity); $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); $this->assertEqual($count, 0, 'NULL field leaves no data in table'); + + // Add a translation in an unavailable language. + $unavailable_language = 'xx'; + db_insert($this->table) + ->fields(array('etid', 'bundle', 'deleted', 'entity_id', 'revision_id', 'delta', 'language')) + ->values(array(_field_sql_storage_etid($entity_type), $this->instance['bundle'], 0, 0, 0, 0, $unavailable_language)) + ->execute(); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 1, 'Field translation in an unavailable language saved'); + + // Again add some real data. + $entity->{$this->field_name}[$language] = array(0 => array('value' => 1)); + field_attach_insert($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 2, 'Field data saved'); + + // Update: Field translation is missing but Field is not empty. Translation data should survive. + $entity->{$this->field_name}[$unavailable_language] = array(mt_rand(1, 127)); + unset($entity->{$this->field_name}[$language]); + field_attach_update($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 2, 'Missing field translation leaves data in table'); + + // Update: Field translation is NULL but Field is not empty. Translation data should be wiped. + $entity->{$this->field_name}[$language] = NULL; + field_attach_update($entity_type, $entity); + $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}")); + $this->assertEqual($count, 1, 'NULL field translation is wiped'); } } Index: modules/field/modules/list/list.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.module,v retrieving revision 1.6 diff -u -p -r1.6 list.module --- modules/field/modules/list/list.module 27 May 2009 18:33:56 -0000 1.6 +++ modules/field/modules/list/list.module 9 Jul 2009 10:36:52 -0000 @@ -103,12 +103,12 @@ function list_field_schema($field) { * Possible error codes: * - 'list_illegal_value': The value is not part of the list of allowed values. */ -function list_field_validate($obj_type, $object, $field, $instance, $items, &$errors) { +function list_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) { $allowed_values = list_allowed_values($field); foreach ($items as $delta => $item) { if (!empty($item['value'])) { if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) { - $errors[$field['field_name']][$delta][] = array( + $errors[$field['field_name']][$language][$delta][] = array( 'error' => 'list_illegal_value', 'message' => t('%name: illegal value.', array('%name' => t($instance['label']))), ); Index: modules/field/modules/number/number.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.module,v retrieving revision 1.10 diff -u -p -r1.10 number.module --- modules/field/modules/number/number.module 28 May 2009 16:44:06 -0000 1.10 +++ modules/field/modules/number/number.module 9 Jul 2009 10:36:52 -0000 @@ -94,17 +94,17 @@ function number_field_schema($field) { * - 'number_min': The value is smaller than the allowed minimum value. * - 'number_max': The value is larger than the allowed maximum value. */ -function number_field_validate($obj_type, $node, $field, $instance, $items, &$errors) { +function number_field_validate($obj_type, $node, $field, $instance, $language, $items, &$errors) { foreach ($items as $delta => $item) { if ($item['value'] != '') { if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) { - $errors[$field['field_name']][$delta][] = array( + $errors[$field['field_name']][$language][$delta][] = array( 'error' => 'number_min', 'message' => t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])), ); } if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) { - $errors[$field['field_name']][$delta][] = array( + $errors[$field['field_name']][$language][$delta][] = array( 'error' => 'number_max', 'message' => t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])), ); Index: modules/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.15 diff -u -p -r1.15 text.module --- modules/field/modules/text/text.module 3 Jul 2009 18:19:29 -0000 1.15 +++ modules/field/modules/text/text.module 9 Jul 2009 10:36:52 -0000 @@ -135,7 +135,7 @@ function text_field_schema($field) { * - 'text_value_max_length': The value exceeds the maximum length. * - 'text_summary_max_length': The summary exceeds the maximum length. */ -function text_field_validate($obj_type, $object, $field, $instance, $items, &$errors) { +function text_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) { foreach ($items as $delta => $item) { foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) { if (!empty($item[$column])) { @@ -148,7 +148,7 @@ function text_field_validate($obj_type, $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])); break; } - $errors[$field['field_name']][$delta][] = array( + $errors[$field['field_name']][$language][$delta][] = array( 'error' => "text_{$column}_length", 'message' => $message, ); @@ -166,9 +166,7 @@ function text_field_validate($obj_type, * separately. * @see text_field_sanitize(). */ -function text_field_load($obj_type, $objects, $field, $instances, &$items) { - global $language; - +function text_field_load($obj_type, $objects, $field, $instances, $language, &$items) { foreach ($objects as $id => $object) { foreach ($items[$id] as $delta => $item) { if (!empty($instances[$id]['settings']['text_processing'])) { @@ -176,10 +174,9 @@ function text_field_load($obj_type, $obj // handled by text_field_sanitize(). $format = $item['format']; if (filter_format_allowcache($format)) { - $lang = isset($object->language) ? $object->language : $language->language; - $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, FALSE, FALSE) : ''; + $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $language, FALSE, FALSE) : ''; if ($field['type'] == 'text_with_summary') { - $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE, FALSE) : ''; + $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $language, FALSE, FALSE) : ''; } } } @@ -198,7 +195,7 @@ function text_field_load($obj_type, $obj * * @see text_field_load() */ -function text_field_sanitize($obj_type, $object, $field, $instance, &$items) { +function text_field_sanitize($obj_type, $object, $field, $instance, $language, &$items) { global $language; foreach ($items as $delta => $item) { // Only sanitize items which were not already processed inside Index: modules/field/modules/text/text.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.test,v retrieving revision 1.7 diff -u -p -r1.7 text.test --- modules/field/modules/text/text.test 12 Jun 2009 08:39:37 -0000 1.7 +++ modules/field/modules/text/text.test 9 Jul 2009 10:36:52 -0000 @@ -50,8 +50,9 @@ class TextFieldTestCase extends DrupalWe field_create_instance($this->instance); // Test valid and invalid values with field_attach_validate(). $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE); + $language = FIELD_LANGUAGE_NEUTRAL; for ($i = 0; $i <= $max_length + 2; $i++) { - $entity->{$this->field['field_name']}[0]['value'] = str_repeat('x', $i); + $entity->{$this->field['field_name']}[$language][0]['value'] = str_repeat('x', $i); try { field_attach_validate('test_entity', $entity); $this->assertTrue($i <= $max_length, "Length $i does not cause validation error when max_length is $max_length"); @@ -91,16 +92,17 @@ class TextFieldTestCase extends DrupalWe ) ); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Display creation form. $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed')); - $this->assertNoFieldByName($this->field_name . '[0][format]', '1', t('Format selector is not displayed')); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed')); + $this->assertNoFieldByName($this->field_name . '[' . $language . '][0][format]', '1', t('Format selector is not displayed')); // Submit with some value. $value = $this->randomName(); $edit = array( - $this->field_name . '[0][value]' => $value, + $this->field_name . '[' . $language . '][0][value]' => $value, ); $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/(\d+)/edit|', $this->url, $match); @@ -143,18 +145,19 @@ class TextFieldTestCase extends DrupalWe ) ); field_create_instance($this->instance); + $language = FIELD_LANGUAGE_NEUTRAL; // Display creation form. // By default, the user only has access to 'Filtered HTML', and no format // selector is displayed $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed')); - $this->assertNoFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is not displayed')); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed')); + $this->assertNoFieldByName($this->field_name . '[' . $language . '][0][value_format]', '1', t('Format selector is not displayed')); // Submit with data that should be filtered. $value = $this->randomName() . '
' . $this->randomName(); $edit = array( - $this->field_name . '[0][value]' => $value, + $this->field_name . '[' . $language . '][0][value]' => $value, ); $this->drupalPost(NULL, $edit, t('Save')); preg_match('|test-entity/(\d+)/edit|', $this->url, $match); @@ -174,12 +177,12 @@ class TextFieldTestCase extends DrupalWe // Display edition form. // We should now have a 'text format' selector. $this->drupalGet('test-entity/' . $id . '/edit'); - $this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed')); - $this->assertFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is displayed')); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value]', '', t('Widget is displayed')); + $this->assertFieldByName($this->field_name . '[' . $language . '][0][value_format]', '1', t('Format selector is displayed')); // Edit and change the format to 'Full HTML'. $edit = array( - $this->field_name . '[0][value_format]' => 2, + $this->field_name . '[' . $language . '][0][value_format]' => 2, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); Index: modules/filter/filter.test =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v retrieving revision 1.26 diff -u -p -r1.26 filter.test --- modules/filter/filter.test 3 Jul 2009 18:26:35 -0000 1.26 +++ modules/filter/filter.test 9 Jul 2009 10:36:52 -0000 @@ -110,8 +110,8 @@ class FilterAdminTestCase extends Drupal $edit = array(); $edit['title'] = $this->randomName(); - $edit['body[0][value]'] = $body . '' . $extra_text . ''; - $edit['body[0][value_format]'] = $filtered; + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body . '' . $extra_text . ''; + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value_format]'] = $filtered; $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.')); Index: modules/forum/forum.test =================================================================== RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v retrieving revision 1.23 diff -u -p -r1.23 forum.test --- modules/forum/forum.test 27 Jun 2009 19:49:07 -0000 1.23 +++ modules/forum/forum.test 9 Jul 2009 10:36:52 -0000 @@ -234,7 +234,7 @@ class ForumTestCase extends DrupalWebTes $edit = array( 'title' => $title, - 'body[0][value]' => $body, + 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => $body, 'taxonomy[1]' => $tid ); @@ -323,7 +323,7 @@ class ForumTestCase extends DrupalWebTes // Edit forum node (including moving it to another forum). $edit = array(); $edit['title'] = 'node/' . $node->nid; - $edit['body[0][value]'] = $this->randomName(256); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(256); $edit['taxonomy[1]'] = $this->root_forum['tid']; // Assumes the topic is initially associated with $forum. $edit['shadow'] = TRUE; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.28 diff -u -p -r1.28 locale.test --- modules/locale/locale.test 30 Jun 2009 18:21:06 -0000 1.28 +++ modules/locale/locale.test 9 Jul 2009 10:36:52 -0000 @@ -1395,7 +1395,7 @@ class LocaleContentFunctionalTest extend $edit = array( 'type' => 'page', 'title' => $node_title, - 'body' => array(array('value' => $node_body)), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $node_body))), 'language' => $langcode, ); $node = $this->drupalCreateNode($edit); Index: modules/node/node.install =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.install,v retrieving revision 1.24 diff -u -p -r1.24 node.install --- modules/node/node.install 18 Jun 2009 15:46:30 -0000 1.24 +++ modules/node/node.install 9 Jul 2009 10:36:52 -0000 @@ -481,15 +481,15 @@ function node_update_7005(&$context) { 'type' => $revision->type, ); if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) { - $node->body[0]['summary'] = $revision->teaser; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['summary'] = $revision->teaser; } // Do this after text_summary() above. $break = ''; if (substr($revision->body, 0, strlen($break)) == $break) { $revision->body = substr($revision->body, strlen($break)); } - $node->body[0]['value'] = $revision->body; - $node->body[0]['format'] = $revision->format; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] = $revision->body; + $node->body[FIELD_LANGUAGE_NEUTRAL][0]['format'] = $revision->format; // This is a core update and no contrib modules are enabled yet, so // we can assume default field storage for a faster update. field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array()); Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1080 diff -u -p -r1.1080 node.module --- modules/node/node.module 7 Jul 2009 13:51:58 -0000 1.1080 +++ modules/node/node.module 9 Jul 2009 10:36:52 -0000 @@ -851,7 +851,7 @@ function node_validate($node, $form = ar // Make sure the body has the minimum number of words. // TODO : use a better word counting algorithm that will work in other languages - if (!empty($type->min_word_count) && isset($node->body[0]['value']) && count(explode(' ', $node->body[0]['value'])) < $type->min_word_count) { + if (!empty($type->min_word_count) && isset($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']) && count(explode(' ', $node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'])) < $type->min_word_count) { // TODO: Use Field API to set this error. form_set_error('body', t('The body of your @type is too short. You need at least %words words.', array('%words' => $type->min_word_count, '@type' => $type->name))); } Index: modules/node/node.test =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.test,v retrieving revision 1.34 diff -u -p -r1.34 node.test --- modules/node/node.test 1 Jul 2009 12:10:32 -0000 1.34 +++ modules/node/node.test 9 Jul 2009 10:36:52 -0000 @@ -141,7 +141,7 @@ class NodeRevisionsTestCase extends Drup // Confirm the correct revision text appears on "view revisions" page. $this->drupalGet("node/$node->nid/revisions/$node->vid/view"); - $this->assertText($node->body[0]['value'], t('Correct text displays for version.')); + $this->assertText($node->body[FIELD_LANGUAGE_NEUTRAL][0]['value'], t('Correct text displays for version.')); // Confirm the correct log message appears on "revisions overview" page. $this->drupalGet("node/$node->nid/revisions"); @@ -155,7 +155,7 @@ class NodeRevisionsTestCase extends Drup array('@type' => 'Page', '%title' => $nodes[1]->title, '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.')); $reverted_node = node_load($node->nid); - $this->assertTrue(($nodes[1]->body[0]['value'] == $reverted_node->body[0]['value']), t('Node reverted correctly.')); + $this->assertTrue(($nodes[1]->body[FIELD_LANGUAGE_NEUTRAL][0]['value'] == $reverted_node->body[FIELD_LANGUAGE_NEUTRAL][0]['value']), t('Node reverted correctly.')); // Confirm revisions delete properly. $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); @@ -186,7 +186,7 @@ class PageEditTestCase extends DrupalWeb * Check node edit functionality. */ function testPageEdit() { - $body_key = 'body[0][value]'; + $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'; // Create node to edit. $edit = array(); $edit['title'] = $this->randomName(8); @@ -241,7 +241,7 @@ class PagePreviewTestCase extends Drupal * Check the node preview functionality. */ function testPagePreview() { - $body_key = 'body[0][value]'; + $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'; // Fill in node creation form and preview node. $edit = array(); @@ -263,7 +263,7 @@ class PagePreviewTestCase extends Drupal * Check the node preview functionality, when using revisions. */ function testPagePreviewWithRevisions() { - $body_key = 'body[0][value]'; + $body_key = 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'; // Force revision on page content. variable_set('node_options_page', array('status', 'revision')); @@ -311,7 +311,7 @@ class PageCreationTestCase extends Drupa // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body[0][value]'] = $this->randomName(16); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the page has been created. @@ -377,7 +377,7 @@ class SummaryLengthTestCase extends Drup function testSummaryLength() { // Create a node to view. $settings = array( - 'body' => array(array('value' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero. Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci. Curabitur feugiat egestas nisl sed accumsan.')), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero. Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci. Curabitur feugiat egestas nisl sed accumsan.'))), 'promote' => 1, ); $node = $this->drupalCreateNode($settings); @@ -500,7 +500,7 @@ class NodePostSettingsTestCase extends D // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body[0][value]'] = $this->randomName(16); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. @@ -521,7 +521,7 @@ class NodePostSettingsTestCase extends D // Create a node. $edit = array(); $edit['title'] = $this->randomName(8); - $edit['body[0][value]'] = $this->randomName(16); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(16); $this->drupalPost('node/add/page', $edit, t('Save')); // Check that the post information is displayed. @@ -687,7 +687,7 @@ class NodeSaveTestCase extends DrupalWeb $title = $this->randomName(8); $node = array( 'title' => $title, - 'body' => array(array('value' => $this->randomName(32))), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(32)))), 'uid' => $this->web_user->uid, 'type' => 'article', 'nid' => $test_nid, Index: modules/path/path.test =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.test,v retrieving revision 1.14 diff -u -p -r1.14 path.test --- modules/path/path.test 30 Jun 2009 18:21:06 -0000 1.14 +++ modules/path/path.test 9 Jul 2009 10:36:52 -0000 @@ -212,7 +212,7 @@ class PathLanguageTestCase extends Drupa $this->clickLink(t('add translation')); $edit = array(); $edit['title'] = $this->randomName(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $edit['path'] = $this->randomName(); $this->drupalPost(NULL, $edit, t('Save')); Index: modules/php/php.test =================================================================== RCS file: /cvs/drupal/drupal/modules/php/php.test,v retrieving revision 1.13 diff -u -p -r1.13 php.test --- modules/php/php.test 27 Jun 2009 02:05:55 -0000 1.13 +++ modules/php/php.test 9 Jul 2009 10:36:52 -0000 @@ -23,7 +23,7 @@ class PHPTestCase extends DrupalWebTestC * @return stdObject Node object. */ function createNodeWithCode() { - return $this->drupalCreateNode(array('body' => array(array('value' => '')))); + return $this->drupalCreateNode(array('body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => ''))))); } } @@ -60,7 +60,7 @@ class PHPFilterTestCase extends PHPTestC // Change filter to PHP filter and see that PHP code is evaluated. $edit = array(); - $edit['body[0][value_format]'] = 3; + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value_format]'] = 3; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.')); Index: modules/search/search.test =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.test,v retrieving revision 1.24 diff -u -p -r1.24 search.test --- modules/search/search.test 3 Jul 2009 19:21:54 -0000 1.24 +++ modules/search/search.test 9 Jul 2009 10:36:52 -0000 @@ -312,7 +312,7 @@ class SearchRankingTestCase extends Drup // Create nodes for testing. foreach ($node_ranks as $node_rank) { - $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => array(array('value' => "Drupal's search rocks"))); + $settings = array('type' => 'page', 'title' => 'Drupal rocks', 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => "Drupal's search rocks")))); foreach (array(0, 1) as $num) { if ($num == 1) { switch ($node_rank) { @@ -321,7 +321,7 @@ class SearchRankingTestCase extends Drup $settings[$node_rank] = 1; break; case 'relevance': - $settings['body'][0]['value'] .= " really rocks"; + $settings['body'][FIELD_LANGUAGE_NEUTRAL][0]['value'] .= " really rocks"; break; case 'recent': $settings['created'] = REQUEST_TIME + 3600; Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.123 diff -u -p -r1.123 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 8 Jul 2009 07:23:23 -0000 1.123 +++ modules/simpletest/drupal_web_test_case.php 9 Jul 2009 10:36:52 -0000 @@ -679,7 +679,7 @@ class DrupalWebTestCase extends DrupalTe protected function drupalCreateNode($settings = array()) { // Populate defaults array. $settings += array( - 'body' => array(array()), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array())), 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, @@ -716,7 +716,7 @@ class DrupalWebTestCase extends DrupalTe 'value' => $this->randomName(32), 'format' => FILTER_FORMAT_DEFAULT ); - $settings['body'][0] += $body; + $settings['body'][FIELD_LANGUAGE_NEUTRAL][0] += $body; $node = (object) $settings; node_save($node); Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.52 diff -u -p -r1.52 common.test --- modules/simpletest/tests/common.test 7 Jul 2009 13:51:58 -0000 1.52 +++ modules/simpletest/tests/common.test 9 Jul 2009 10:36:52 -0000 @@ -271,7 +271,7 @@ class CascadingStylesheetsTestCase exten // Create a node, using the PHP filter that tests drupal_add_css(). $settings = array( 'type' => 'page', - 'body' => array(array('value' => t('This tests the inline CSS!') . "", 'format' => 3)), // PHP filter. + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => t('This tests the inline CSS!') . "", 'format' => 3))), // PHP filter. 'promote' => 1, ); $node = $this->drupalCreateNode($settings); Index: modules/simpletest/tests/field_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v retrieving revision 1.12 diff -u -p -r1.12 field_test.module --- modules/simpletest/tests/field_test.module 8 Jul 2009 07:19:05 -0000 1.12 +++ modules/simpletest/tests/field_test.module 9 Jul 2009 13:50:13 -0000 @@ -84,6 +84,24 @@ function field_test_fieldable_info() { } /** + * Implementation of hook_fieldable_info_alter. + */ +function field_test_fieldable_info_alter(&$info) { + $info['test_entity']['translatable'] = field_test_fieldable_info_translatable(); +} + +/** + * Helper function to enable entity translations. + */ +function field_test_fieldable_info_translatable($translatable = NULL) { + $stored_value = &drupal_static(__FUNCTION__, FALSE); + if (isset($translatable)) { + $stored_value = $translatable; + } + return $stored_value; +} + +/** * Create a new bundle for test_entity objects. * * @param $bundle @@ -368,7 +386,7 @@ function field_test_field_schema($field) /** * Implement hook_field_load(). */ -function field_test_field_load($obj_type, $objects, $field, $instances, &$items, $age) { +function field_test_field_load($obj_type, $objects, $field, $instances, $language, &$items, $age) { foreach ($items as $id => $item) { // To keep the test non-intrusive, only act for instances with the // test_hook_field_load setting explicitly set to TRUE. @@ -389,10 +407,10 @@ function field_test_field_load($obj_type * Possible error codes: * - 'field_test_invalid': The value is invalid. */ -function field_test_field_validate($obj_type, $object, $field, $instance, $items, &$errors) { +function field_test_field_validate($obj_type, $object, $field, $instance, $language, $items, &$errors) { foreach ($items as $delta => $item) { if ($item['value'] == -1) { - $errors[$field['field_name']][$delta][] = array( + $errors[$field['field_name']][$language][$delta][] = array( 'error' => 'field_test_invalid', 'message' => t('%name does not accept the value -1.', array('%name' => $instance['label'])), ); @@ -403,7 +421,7 @@ function field_test_field_validate($obj_ /** * Implement hook_field_sanitize(). */ -function field_test_field_sanitize($obj_type, $object, $field, $instance, &$items) { +function field_test_field_sanitize($obj_type, $object, $field, $instance, $language, &$items) { foreach ($items as $delta => $item) { $value = check_plain($item['value']); $items[$delta]['safe'] = $value; @@ -474,8 +492,8 @@ function field_test_field_widget_info() * holds the field's form values. * @param $field * The field structure. - * @param $insatnce - * the insatnce array + * @param $instance + * the instance array * @param $items * array of default values for this field * @param $delta @@ -577,6 +595,36 @@ function field_test_default_value($obj_t } /** + * Generic op to test _field_invoke behavior. + */ +function field_test_field_test_op($obj_type, $object, $field, $instance, $language, &$items) { + return array($language => md5(serialize(array($obj_type, $object, $field['field_name'], $language, $items)))); +} + +/** + * Generic op to test _field_invoke_multiple behavior. + */ +function field_test_field_test_op_multiple($obj_type, $objects, $field, $instances, $language, &$items) { + $result = array(); + foreach ($objects as $id => $object) { + $result[$id] = array($language => md5(serialize(array($obj_type, $object, $field['field_name'], $language, $items[$id])))); + } + return $result; +} + +/** + * Implementation of hook_field_languages + */ +function field_test_field_languages($obj_type, $field, &$languages) { + if ($field['settings']['test_hook_in']) { + // Add an unavailable language. + $languages[] = 'xx'; + // Remove an available language. + unset($languages[0]); + } +} + +/** * Store and retrieve keyed data for later verification by unit tests. * * This function is a simple in-memory key-value store with the Index: modules/system/system.test =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.test,v retrieving revision 1.54 diff -u -p -r1.54 system.test --- modules/system/system.test 1 Jul 2009 08:39:55 -0000 1.54 +++ modules/system/system.test 9 Jul 2009 10:36:52 -0000 @@ -520,7 +520,7 @@ class AccessDeniedTestCase extends Drupa $edit = array( 'title' => $this->randomName(10), - 'body' => array(array('value' => $this->randomName(100))), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(100)))), ); $node = $this->drupalCreateNode($edit); @@ -581,7 +581,7 @@ class PageNotFoundTestCase extends Drupa $edit = array( 'title' => $this->randomName(10), - 'body' => array(array('value' => $this->randomName(100))), + 'body' => array(FIELD_LANGUAGE_NEUTRAL => array(array('value' => $this->randomName(100)))), ); $node = $this->drupalCreateNode($edit); @@ -707,7 +707,7 @@ class PageTitleFiltering extends DrupalW // Generate node content. $edit = array( 'title' => '!SimpleTest! ' . $title . $this->randomName(20), - 'body[0][value]' => '!SimpleTest! test body' . $this->randomName(200), + 'body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]' => '!SimpleTest! test body' . $this->randomName(200), ); // Create the node with HTML in the title. $this->drupalPost('node/add/page', $edit, t('Save')); Index: modules/taxonomy/taxonomy.test =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v retrieving revision 1.38 diff -u -p -r1.38 taxonomy.test --- modules/taxonomy/taxonomy.test 27 Jun 2009 19:49:07 -0000 1.38 +++ modules/taxonomy/taxonomy.test 9 Jul 2009 10:36:52 -0000 @@ -477,7 +477,7 @@ class TaxonomyTermTestCase extends Taxon // Post an article. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid; $this->drupalPost('node/add/article', $edit, t('Save')); @@ -519,7 +519,7 @@ class TaxonomyTermTestCase extends Taxon // Insert the terms in a comma separated list. Vocabulary 1 is a // free-tagging field created by the default profile. $edit['taxonomy[tags][' . $this->vocabulary->vid . ']'] = implode(', ', $terms); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $this->drupalPost('node/add/article', $edit, t('Save')); $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully')); foreach ($terms as $term) { Index: modules/translation/translation.test =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v retrieving revision 1.12 diff -u -p -r1.12 translation.test --- modules/translation/translation.test 12 Jun 2009 08:39:40 -0000 1.12 +++ modules/translation/translation.test 9 Jul 2009 10:36:52 -0000 @@ -60,14 +60,14 @@ class TranslationTestCase extends Drupal // to return to the page then resubmitting the form without a refresh. $edit = array(); $edit['title'] = $this->randomName(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $this->drupalPost('node/add/page', $edit, t('Save'), array('query' => array('translation' => $node->nid, 'language' => 'es'))); $duplicate = $this->drupalGetNodeByTitle($edit['title']); $this->assertEqual($duplicate->tnid, 0, t('The node does not have a tnid.')); // Update original and mark translation as outdated. $edit = array(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $edit['translation[retranslate]'] = TRUE; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Original node updated.')); @@ -78,7 +78,7 @@ class TranslationTestCase extends Drupal // Update translation and mark as updated. $edit = array(); - $edit['body[0][value]'] = $this->randomName(); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $this->randomName(); $edit['translation[status]'] = FALSE; $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.')); @@ -128,7 +128,7 @@ class TranslationTestCase extends Drupal function createPage($title, $body, $language) { $edit = array(); $edit['title'] = $title; - $edit['body[0][value]'] = $body; + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body; $edit['language'] = $language; $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Page created.')); @@ -153,7 +153,7 @@ class TranslationTestCase extends Drupal $edit = array(); $edit['title'] = $title; - $edit['body[0][value]'] = $body; + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = $body; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.')); Index: modules/trigger/trigger.test =================================================================== RCS file: /cvs/drupal/drupal/modules/trigger/trigger.test,v retrieving revision 1.12 diff -u -p -r1.12 trigger.test --- modules/trigger/trigger.test 12 Jun 2009 08:39:40 -0000 1.12 +++ modules/trigger/trigger.test 9 Jul 2009 10:36:52 -0000 @@ -38,7 +38,7 @@ class TriggerContentTestCase extends Dru $this->drupalLogin($web_user); $edit = array(); $edit['title'] = '!SimpleTest test node! ' . $this->randomName(10); - $edit['body[0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit['body[' . FIELD_LANGUAGE_NEUTRAL . '][0][value]'] = '!SimpleTest test body! ' . $this->randomName(32) . ' ' . $this->randomName(32); $edit[$info['property']] = !$info['expected']; $this->drupalPost('node/add/page', $edit, t('Save')); // Make sure the text we want appears.