summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/aggregator/aggregator.test3
-rw-r--r--modules/blog/blog.test3
-rw-r--r--modules/blogapi/blogapi.module16
-rw-r--r--modules/book/book.test5
-rw-r--r--modules/dblog/dblog.test6
-rw-r--r--modules/field/field.api.php86
-rw-r--r--modules/field/field.attach.inc192
-rw-r--r--modules/field/field.crud.inc3
-rw-r--r--modules/field/field.default.inc25
-rw-r--r--modules/field/field.form.inc60
-rw-r--r--modules/field/field.info1
-rw-r--r--modules/field/field.info.inc1
-rw-r--r--modules/field/field.install6
-rw-r--r--modules/field/field.module16
-rw-r--r--modules/field/field.multilingual.inc123
-rw-r--r--modules/field/field.test459
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module94
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.test85
-rw-r--r--modules/field/modules/list/list.module4
-rw-r--r--modules/field/modules/number/number.module6
-rw-r--r--modules/field/modules/text/text.module21
-rw-r--r--modules/field/modules/text/text.test23
-rw-r--r--modules/field/theme/field.tpl.php2
-rw-r--r--modules/field_ui/field_ui.admin.inc2
-rw-r--r--modules/filter/filter.test5
-rw-r--r--modules/forum/forum.test6
-rw-r--r--modules/locale/locale.test3
-rw-r--r--modules/node/node.install6
-rw-r--r--modules/node/node.pages.inc2
-rw-r--r--modules/node/node.test26
-rw-r--r--modules/node/node.tpl.php7
-rw-r--r--modules/path/path.test4
-rw-r--r--modules/php/php.test5
-rw-r--r--modules/search/search.test4
-rw-r--r--modules/simpletest/drupal_web_test_case.php4
-rw-r--r--modules/simpletest/tests/common.test2
-rw-r--r--modules/simpletest/tests/field_test.module63
-rw-r--r--modules/system/system.test7
-rw-r--r--modules/taxonomy/taxonomy.module4
-rw-r--r--modules/taxonomy/taxonomy.test20
-rw-r--r--modules/translation/translation.test13
-rw-r--r--modules/trigger/trigger.test3
-rw-r--r--modules/user/user-profile.tpl.php7
43 files changed, 1076 insertions, 357 deletions
diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test
index 726fbdc49..c0b59b3cd 100644
--- a/modules/aggregator/aggregator.test
+++ b/modules/aggregator/aggregator.test
@@ -248,11 +248,12 @@ EOF;
}
function createSampleNodes() {
+ $langcode = FIELD_LANGUAGE_NONE;
// Post 5 articles.
for ($i = 0; $i < 5; $i++) {
$edit = array();
$edit['title'] = $this->randomName();
- $edit['body[0][value]'] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
$this->drupalPost('node/add/article', $edit, t('Save'));
}
}
diff --git a/modules/blog/blog.test b/modules/blog/blog.test
index fea8d6edb..a5def9a61 100644
--- a/modules/blog/blog.test
+++ b/modules/blog/blog.test
@@ -152,7 +152,8 @@ class BlogTestCase extends DrupalWebTestCase {
// Edit blog node.
$edit = array();
$edit['title'] = 'node/' . $node->nid;
- $edit['body[0][value]'] = $this->randomName(256);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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'));
diff --git a/modules/blogapi/blogapi.module b/modules/blogapi/blogapi.module
index 5a6ab9efe..b17c36b9d 100644
--- a/modules/blogapi/blogapi.module
+++ b/modules/blogapi/blogapi.module
@@ -213,7 +213,7 @@ function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $conte
}
else {
$edit['title'] = blogapi_blogger_title($content);
- $edit['body'][0]['value'] = $content;
+ $edit['body'][FIELD_LANGUAGE_NONE][0]['value'] = $content;
}
if (!node_access('create', $edit['type'])) {
@@ -274,12 +274,12 @@ function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $cont
// Check for bloggerAPI vs. metaWeblogAPI.
if (is_array($content)) {
$node->title = $content['title'];
- $node->body[0]['value'] = $content['description'];
+ $node->body[FIELD_LANGUAGE_NONE][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_NONE][0]['value'] = $content;
}
module_invoke_all('node_blogapi_edit', $node);
@@ -895,14 +895,14 @@ function _blogapi_mt_extra($node, $struct) {
// 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'] . '<!--break-->' . $node->body[0]['value'];
+ $node->body[FIELD_LANGUAGE_NONE][0]['value'] = $struct['mt_excerpt'] . '<!--break-->' . $node->body[FIELD_LANGUAGE_NONE][0]['value'];
}
if ($struct['mt_text_more']) {
- $node->body[0]['value'] = $node->body[0]['value'] . '<!--extended-->' . $struct['mt_text_more'];
+ $node->body[FIELD_LANGUAGE_NONE][0]['value'] = $node->body[FIELD_LANGUAGE_NONE][0]['value'] . '<!--extended-->' . $struct['mt_text_more'];
}
if ($struct['mt_convert_breaks']) {
- $node->body[0]['format'] = $struct['mt_convert_breaks'];
+ $node->body[FIELD_LANGUAGE_NONE][0]['format'] = $struct['mt_convert_breaks'];
}
if ($struct['dateCreated']) {
@@ -925,8 +925,8 @@ function _blogapi_get_post($node, $bodies = TRUE) {
);
if ($bodies) {
- $body = $node->body[0]['value'];
- $format = $node->body[0]['format'];
+ $body = $node->body[FIELD_LANGUAGE_NONE][0]['value'];
+ $format = $node->body[FIELD_LANGUAGE_NONE][0]['format'];
if ($node->comment == 1) {
$comment = 2;
}
diff --git a/modules/book/book.test b/modules/book/book.test
index 8b699e344..b577cf05d 100644
--- a/modules/book/book.test
+++ b/modules/book/book.test
@@ -141,7 +141,7 @@ class BookTestCase extends DrupalWebTestCase {
// 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_NONE][0]['value'], $node->body[FIELD_LANGUAGE_NONE][0]['format']), t('Printer friendly body found.'));
$number++;
}
@@ -173,7 +173,8 @@ class BookTestCase extends DrupalWebTestCase {
$edit = array();
$edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10);
- $edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32);
$edit['book[bid]'] = $book_nid;
if ($parent !== NULL) {
diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test
index 342bba0e9..8e8caa4a3 100644
--- a/modules/dblog/dblog.test
+++ b/modules/dblog/dblog.test
@@ -330,9 +330,10 @@ class DBLogTestCase extends DrupalWebTestCase {
break;
default:
+ $langcode = FIELD_LANGUAGE_NONE;
$content = array(
'title' => $this->randomName(8),
- 'body[0][value]' => $this->randomName(32),
+ "body[$langcode][0][value]" => $this->randomName(32),
);
break;
}
@@ -355,8 +356,9 @@ class DBLogTestCase extends DrupalWebTestCase {
break;
default:
+ $langcode = FIELD_LANGUAGE_NONE;
$content = array(
- 'body[0][value]' => $this->randomName(32),
+ "body[$langcode][0][value]" => $this->randomName(32),
);
break;
}
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 7c78f889f..64174560b 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -350,6 +350,8 @@ function hook_field_schema($field) {
* The field structure for the operation.
* @param $instances
* Array of instance structures for $field for each object, keyed by object id.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
* Array of field values already loaded for the objects, keyed by object id.
* @param $age
@@ -359,9 +361,7 @@ function hook_field_schema($field) {
* Changes or additions to field values are done by altering the $items
* parameter by reference.
*/
-function hook_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
- global $language;
-
+function hook_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
foreach ($objects as $id => $object) {
foreach ($items[$id] as $delta => $item) {
if (!empty($instances[$id]['settings']['text_processing'])) {
@@ -369,10 +369,9 @@ function hook_field_load($obj_type, $objects, $field, $instances, &$items, $age)
// handled by hook_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) : '';
+ $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode, FALSE) : '';
if ($field['type'] == 'text_with_summary') {
- $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE) : '';
+ $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode, FALSE) : '';
}
}
}
@@ -401,11 +400,12 @@ function hook_field_load($obj_type, $objects, $field, $instances, &$items, $age)
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
* $object->{$field['field_name']}, or an empty array if unset.
*/
-function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
- global $language;
+function hook_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
// Only sanitize items which were not already processed inside
// hook_field_load(), i.e. items with uncacheable text formats, or coming
@@ -413,10 +413,9 @@ function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
if (!isset($items[$delta]['safe'])) {
if (!empty($instance['settings']['text_processing'])) {
$format = $item['format'];
- $lang = isset($object->language) ? $object->language : $language->language;
- $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang) : '';
+ $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode) : '';
if ($field['type'] == 'text_with_summary') {
- $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang) : '';
+ $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode) : '';
}
}
else {
@@ -444,8 +443,10 @@ function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
* @param $errors
* The array of errors, keyed by field name and by value delta, that have
* already been reported for the object. The function should add its errors
@@ -454,7 +455,7 @@ function hook_field_sanitize($obj_type, $object, $field, $instance, $items) {
* - 'error': an error code (should be a string, prefixed with the module name)
* - 'message': the human readable message to be displayed.
*/
-function hook_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
+function hook_field_validate($obj_type, $object, $field, $instance, $langcode, &$items, &$errors) {
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
@@ -478,10 +479,12 @@ function hook_field_validate($obj_type, $object, $field, $instance, $items, &$er
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_presave($obj_type, $object, $field, $instance, $items) {
+function hook_field_presave($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -495,10 +498,12 @@ function hook_field_presave($obj_type, $object, $field, $instance, $items) {
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_insert($obj_type, $object, $field, $instance, $items) {
+function hook_field_insert($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -512,10 +517,12 @@ function hook_field_insert($obj_type, $object, $field, $instance, $items) {
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_update($obj_type, $object, $field, $instance, $items) {
+function hook_field_update($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -531,10 +538,12 @@ function hook_field_update($obj_type, $object, $field, $instance, $items) {
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_delete($obj_type, $object, $field, $instance, $items) {
+function hook_field_delete($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -551,10 +560,12 @@ function hook_field_delete($obj_type, $object, $field, $instance, $items) {
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_delete_revision($obj_type, $object, $field, $instance, $items) {
+function hook_field_delete_revision($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -570,10 +581,12 @@ function hook_field_delete_revision($obj_type, $object, $field, $instance, $item
* The field structure for the operation.
* @param $instance
* The instance structure for $field on $object's bundle.
+ * @param $langcode
+ * The language associated to $items.
* @param $items
- * $object->{$field['field_name']}, or an empty array if unset.
+ * $object->{$field['field_name']}[$langcode], or an empty array if unset.
*/
-function hook_field_prepare_translation($obj_type, $object, $field, $instance, $items) {
+function hook_field_prepare_translation($obj_type, $object, $field, $instance, $langcode, &$items) {
}
/**
@@ -902,7 +915,7 @@ function theme_field_formatter_FORMATTER_MULTIPLE($element) {
*
* See field_attach_form() for details and arguments.
*/
-function hook_field_attach_form($obj_type, $object, &$form, &$form_state) {
+function hook_field_attach_form($obj_type, $object, &$form, &$form_state, $langcode) {
}
/**
@@ -988,6 +1001,23 @@ function hook_field_attach_presave($obj_type, $object) {
}
/**
+ * Act on field_attach_preprocess.
+ *
+ * This hook is invoked while preprocessing the field.tpl.php template file.
+ *
+ * @param $variables
+ * The variables array is passed by reference and will be populated with field values.
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to render.
+ * @param $element
+ * The structured array containing the values ready for rendering.
+ */
+function hook_field_attach_preprocess_alter(&$variables, $obj_type, $object, $element) {
+}
+
+/**
* Act on field_attach_insert.
*
* This hook allows modules to store data before the Field Storage
@@ -1094,8 +1124,10 @@ function hook_field_attach_delete_revision($obj_type, $object) {
* The object with fields to render.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * The language in which the field values will be displayed.
*/
-function hook_field_attach_view_alter($output, $obj_type, $object, $build_mode) {
+function hook_field_attach_view_alter($output, $obj_type, $object, $build_mode, $langcode) {
}
/**
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index 838c6d93c..b061851ac 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -176,6 +176,7 @@ function _field_invoke($op, $obj_type, $object, &$a = NULL, &$b = NULL, $options
$default_options = array(
'default' => FALSE,
'deleted' => FALSE,
+ 'language' => NULL,
);
$options += $default_options;
@@ -196,32 +197,38 @@ function _field_invoke($op, $obj_type, $object, &$a = NULL, &$b = NULL, $options
// When in 'single field' mode, only act on the specified field.
if ((!isset($options['field_id']) || $options['field_id'] == $instance['field_id']) && (!isset($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_multilingual_available_languages($obj_type, $field, $suggested_languages) as $langcode) {
+ $field_translations[$langcode] = isset($object->{$field_name}[$langcode]) ? $object->{$field_name}[$langcode] : 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 $langcode => $items) {
+ $result = $function($obj_type, $object, $field, $instance, $langcode, $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}[$langcode])) {
+ $object->{$field_name}[$langcode] = $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;
- }
}
}
@@ -273,6 +280,7 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL
$default_options = array(
'default' => FALSE,
'deleted' => FALSE,
+ 'language' => NULL,
);
$options += $default_options;
@@ -314,7 +322,10 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL
$grouped_objects[$field_id][$id] = $objects[$id];
// Extract the field values into a separate variable, easily accessed
// by hook implementations.
- $grouped_items[$field_id][$id] = isset($object->$field_name) ? $object->$field_name : array();
+ $suggested_languages = empty($options['language']) ? NULL : array($options['language']);
+ foreach (field_multilingual_available_languages($obj_type, $fields[$field_id], $suggested_languages) as $langcode) {
+ $grouped_items[$field_id][$langcode][$id] = isset($object->{$field_name}[$langcode]) ? $object->{$field_name}[$langcode] : array();
+ }
}
}
// Initialize the return value for each object.
@@ -326,17 +337,20 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL
$field_name = $field['field_name'];
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (drupal_function_exists($function)) {
- $results = $function($obj_type, $grouped_objects[$field_id], $field, $grouped_instances[$field_id], $grouped_items[$field_id], $options, $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_id] as $langcode => $items) {
+ $results = $function($obj_type, $grouped_objects[$field_id], $field, $grouped_instances[$field_id], $langcode, $grouped_items[$field_id][$langcode], $options, $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;
+ }
}
}
}
@@ -345,8 +359,10 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL
// 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_id] as $id => $object) {
- if ($grouped_items[$field_id][$id] !== array() || property_exists($object, $field_name)) {
- $object->$field_name = $grouped_items[$field_id][$id];
+ foreach ($grouped_items[$field_id] as $langcode => $items) {
+ if ($grouped_items[$field_id][$langcode][$id] !== array() || isset($object->{$field_name}[$langcode])) {
+ $object->{$field_name}[$langcode] = $grouped_items[$field_id][$langcode][$id];
+ }
}
}
}
@@ -394,6 +410,9 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$
* The form structure to fill in.
* @param $form_state
* An associative array containing the current state of the form.
+ * @param $langcode
+ * The language the field values are going to be entered, if no language
+ * is provided the default site language will be used.
* @return
* The form elements are added by reference at the top level of the $form
* parameter. Sample structure:
@@ -417,45 +436,57 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$
* // most common case), and will therefore be repeated as many times as
* // needed, or 'multiple-values' (one single widget allows the input of
* // several values, e.g checkboxes, select box...).
+ * // The sub-array is nested into a $langcode key where $langcode has the
+ * // same value of the $langcode parameter above. This allow us to match
+ * // the field data structure ($field_name[$langcode][$delta][$column]).
+ * // The '#language' key holds the same value of $langcode and it is used
+ * // to access the field sub-array when $langcode is unknown.
* 'field_foo' => array(
- * '#field_name' => the name of the field,
* '#tree' => TRUE,
- * '#required' => whether or not the field is required,
- * '#title' => the label of the field instance,
- * '#description' => the description text for the field instance,
- *
- * // Only for 'single' widgets:
- * '#theme' => 'field_multiple_value_form',
- * '#multiple' => the field cardinality,
- * // One sub-array per copy of the widget, keyed by delta.
- * 0 => array(
- * '#title' => the title to be displayed by the widget,
- * '#default_value' => the field value for delta 0,
- * '#required' => whether the widget should be marked required,
- * '#delta' => 0,
+ * '#language' => $langcode,
+ * $langcode => array(
* '#field_name' => the name of the field,
- * '#bundle' => the name of the bundle,
- * '#columns' => the array of field columns,
+ * '#tree' => TRUE,
+ * '#required' => whether or not the field is required,
+ * '#title' => the label of the field instance,
+ * '#description' => the description text for the field instance,
+ *
+ * // Only for 'single' widgets:
+ * '#theme' => 'field_multiple_value_form',
+ * '#multiple' => the field cardinality,
+ * // One sub-array per copy of the widget, keyed by delta.
+ * 0 => array(
+ * '#title' => the title to be displayed by the widget,
+ * '#default_value' => the field value for delta 0,
+ * '#required' => whether the widget should be marked required,
+ * '#delta' => 0,
+ * '#field_name' => the name of the field,
+ * '#bundle' => the name of the bundle,
+ * '#columns' => the array of field columns,
+ * // The remaining elements in the sub-array depend on the widget.
+ * '#type' => the type of the widget,
+ * ...
+ * ),
+ * 1 => array(
+ * ...
+ * ),
+ *
+ * // Only for multiple widgets:
+ * '#bundle' => $instance['bundle'],
+ * '#columns' => array_keys($field['columns']),
* // The remaining elements in the sub-array depend on the widget.
* '#type' => the type of the widget,
* ...
* ),
- * 1 => array(
- * ...
- * ),
- *
- * // Only for multiple widgets:
- * '#bundle' => $instance['bundle'],
- * '#columns' => array_keys($field['columns']),
- * // The remaining elements in the sub-array depend on the widget.
- * '#type' => the type of the widget,
* ...
* ),
* )
* @endcode
*/
-function field_attach_form($obj_type, $object, &$form, &$form_state) {
- $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state);
+function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode = NULL) {
+ // If no language is provided use the default site language.
+ $options = array('language' => field_multilingual_valid_language($langcode));
+ $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state, $options);
// Add custom weight handling.
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
@@ -991,6 +1022,9 @@ function field_attach_query_revisions($field_id, $conditions, $count, &$cursor =
* The object with fields to render.
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
+ * @param $langcode
+ * The language the field values are to be shown in. If no language is
+ * provided the current language is used.
* @return
* A structured content array tree for drupal_render().
* Sample structure:
@@ -1042,11 +1076,15 @@ function field_attach_query_revisions($field_id, $conditions, $count, &$cursor =
* );
* @endcode
*/
-function field_attach_view($obj_type, $object, $build_mode = 'full') {
+function field_attach_view($obj_type, $object, $build_mode = 'full', $langcode = NULL) {
+ // If no language is provided use the current UI language.
+ $options = array('language' => field_multilingual_valid_language($langcode, FALSE));
+
// Let field modules sanitize their data for output.
- _field_invoke('sanitize', $obj_type, $object);
+ $null = NULL;
+ _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);
// Add custom weight handling.
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
@@ -1057,7 +1095,39 @@ function field_attach_view($obj_type, $object, $build_mode = 'full') {
drupal_alter('field_attach_view', $output, $obj_type, $object, $build_mode);
return $output;
+}
+
+/**
+ * Populate the template variables with the field values available for rendering.
+ *
+ * The $variables array will be populated with all the field instance values
+ * associated with the given entity type, keyed by field name; in case of
+ * translatable fields the language currently chosen for display will be
+ * selected.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object with fields to render.
+ * @param $element
+ * The structured array containing the values ready for rendering.
+ * @param $variables
+ * The variables array is passed by reference and will be populated with field
+ * values.
+ */
+function field_attach_preprocess($obj_type, $object, $element, &$variables) {
+ list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ if (isset($element[$field_name]['#language'])) {
+ $langcode = $element[$field_name]['#language'];
+ $variables[$field_name] = isset($object->{$field_name}[$langcode]) ? $object->{$field_name}[$langcode] : NULL;
+ }
+ }
+ // Let other modules make changes to the $variables array.
+ drupal_alter('field_attach_preprocess', $variables, $obj_type, $object, $element);
}
/**
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index cea858d53..f559e8536 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -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)
@@ -237,6 +239,7 @@ function field_create_field($field) {
$field += array(
'cardinality' => 1,
+ 'translatable' => FALSE,
'locked' => FALSE,
'settings' => array(),
);
diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc
index 2c5e175b4..e8db86c5b 100644
--- a/modules/field/field.default.inc
+++ b/modules/field/field.default.inc
@@ -11,17 +11,17 @@
* 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, $langcode, &$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][$langcode])) {
+ $items = $form_state['values'][$field_name][$langcode];
// Remove the 'value' of the 'add more' button.
unset($items[$field_name . '_add_more']);
}
}
-function field_default_submit($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
+function field_default_submit($obj_type, $object, $field, $instance, $langcode, &$items, $form, &$form_state) {
$field_name = $field['field_name'];
// Reorder items to account for drag-n-drop reordering.
@@ -40,19 +40,25 @@ function field_default_submit($obj_type, $object, $field, $instance, &$items, $f
* 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, $langcode, &$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 (empty($object) || !property_exists($object, $field['field_name'])) {
- $items = field_get_default_value($obj_type, $object, $field, $instance);
+ // We also check that the current field translation is actually defined before
+ // assigning it a default value. This way we ensure that only the intended
+ // languages get a default value. Otherwise we could have default values for
+ // not yet open languages.
+ if (empty($object) || !property_exists($object, $field['field_name']) ||
+ (isset($object->{$field['field_name']}[$langcode]) && count($object->{$field['field_name']}[$langcode]) == 0)) {
+ $items = field_get_default_value($obj_type, $object, $field, $instance, $langcode);
}
}
+
/**
* Default field 'view' operation.
*
* @see field_attach_view()
*/
-function field_default_view($obj_type, $object, $field, $instance, $items, $build_mode) {
+function field_default_view($obj_type, $object, $field, $instance, $langcode, $items, $build_mode) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
$addition = array();
@@ -82,6 +88,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $buil
'#label_display' => $label_display,
'#build_mode' => $build_mode,
'#single' => $single,
+ '#language' => $langcode,
'items' => array(),
);
@@ -117,7 +124,7 @@ function field_default_view($obj_type, $object, $field, $instance, $items, $buil
return $addition;
}
-function field_default_prepare_translation($obj_type, $object, $field, $instance, &$items) {
+function field_default_prepare_translation($obj_type, $object, $field, $instance, $langcode, &$items) {
$addition = array();
if (isset($object->translation_source->$field['field_name'])) {
$addition[$field['field_name']] = $object->translation_source->$field['field_name'];
diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc
index 0bc155bc4..17d79a7a7 100644
--- a/modules/field/field.form.inc
+++ b/modules/field/field.form.inc
@@ -9,7 +9,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, $langcode, $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) {
@@ -48,7 +48,7 @@ function field_default_form($obj_type, $object, $field, $instance, $items, &$for
// 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, $langcode, $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
@@ -89,7 +89,20 @@ function field_default_form($obj_type, $object, $field, $instance, $items, &$for
'#weight' => $instance['widget']['weight'],
);
- $addition[$field['field_name']] = array_merge($form_element, $defaults);
+ $form_element = array_merge($form_element, $defaults);
+
+ // Add the field form element as a child keyed by language code to match the
+ // field data structure: $object->{$field_name}[$langcode][$delta][$column].
+ // The '#language' key can be used to access the field's form element when
+ // $langcode is unknown. The #weight property is inherited from the field's
+ // form element.
+ $addition[$field['field_name']] = array(
+ '#tree' => TRUE,
+ '#weight' => $form_element['#weight'],
+ '#language' => $langcode,
+ $langcode => $form_element,
+ );
+
$form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
}
@@ -104,7 +117,7 @@ function field_default_form($obj_type, $object, $field, $instance, $items, &$for
* - 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, $langcode, $items, &$form, &$form_state) {
$field = field_info_field($instance['field_name']);
$field_name = $field['field_name'];
@@ -197,9 +210,11 @@ function field_multiple_value_form($field, $instance, $items, &$form, &$form_sta
'#field_name' => $field_name,
'#bundle' => $instance['bundle'],
'#attributes' => array('class' => 'field-add-more-submit'),
+ '#language' => $langcode,
);
}
}
+
return $form_element;
}
@@ -276,9 +291,9 @@ function theme_field_multiple_value_form($element) {
/**
* 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, $langcode, $items, $form, $errors) {
$field_name = $field['field_name'];
- if (!empty($errors[$field_name])) {
+ if (!empty($errors[$field_name][$langcode])) {
$function = $instance['widget']['module'] . '_field_widget_error';
$function_exists = drupal_function_exists($function);
@@ -290,10 +305,10 @@ function field_default_form_errors($obj_type, $object, $field, $instance, $items
}
$multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT;
- foreach ($errors[$field_name] as $delta => $delta_errors) {
+ foreach ($errors[$field_name][$langcode] 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[$langcode] : $element[$langcode][$delta];
foreach ($delta_errors as $error) {
if ($function_exists) {
$function($error_element, $error);
@@ -320,8 +335,9 @@ function field_add_more_submit($form, &$form_state) {
// Make the changes we want to the form state.
$field_name = $form_state['clicked_button']['#field_name'];
+ $langcode = $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][$langcode]);
}
}
}
@@ -383,19 +399,25 @@ function field_add_more_js($bundle_name, $field_name) {
// 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.
+ $langcode = key($_POST[$field_name]);
+ if ($langcode != FIELD_LANGUAGE_NONE) {
+ $langcode = field_multilingual_valid_language($langcode);
+ }
+
// 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][$langcode][$field['field_name'] . '_add_more']);
+ foreach ($_POST[$field_name][$langcode] as $delta => $item) {
+ $form_state['values'][$field_name][$langcode][$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][$langcode] = _field_sort_items($field, $form_state['values'][$field_name][$langcode]);
+ $_POST[$field_name][$langcode] = _field_sort_items($field, $_POST[$field_name][$langcode]);
// 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][$langcode]) + 1);
+ $items = $form_state['values'][$field_name][$langcode];
+ $form_element = field_default_form(NULL, NULL, $field, $instance, $langcode, $items, $form, $form_state);
// Let other modules alter it.
drupal_alter('form', $form_element, array(), 'field_add_more_js');
@@ -412,8 +434,8 @@ function field_add_more_js($bundle_name, $field_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][$langcode])) + 1;
+ $_POST[$field_name][$langcode][$delta]['_weight'] = $delta;
$form_state = form_state_defaults();
$form_state['input'] = $_POST;
$form = form_builder($_POST['form_id'], $form, $form_state);
diff --git a/modules/field/field.info b/modules/field/field.info
index 9c06c1760..04c516027 100644
--- a/modules/field/field.info
+++ b/modules/field/field.info
@@ -9,6 +9,7 @@ files[] = field.install
files[] = field.crud.inc
files[] = field.info.inc
files[] = field.default.inc
+files[] = field.multilingual.inc
files[] = field.attach.inc
files[] = field.form.inc
files[] = field.test
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index 685edd2d4..3c9c1df3e 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -130,6 +130,7 @@ function _field_info_collate_types($reset = FALSE) {
// Provide defaults.
$fieldable_info += array(
'cacheable' => TRUE,
+ 'translation_handlers' => array(),
'bundles' => array(),
);
$fieldable_info['object keys'] += array(
diff --git a/modules/field/field.install b/modules/field/field.install
index 2d3b3750e..a7b9df27b 100644
--- a/modules/field/field.install
+++ b/modules/field/field.install
@@ -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',
diff --git a/modules/field/field.module b/modules/field/field.module
index 94aa34efe..f73acc360 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -12,6 +12,7 @@
*/
require(DRUPAL_ROOT . '/modules/field/field.crud.inc');
require(DRUPAL_ROOT . '/modules/field/field.info.inc');
+require(DRUPAL_ROOT . '/modules/field/field.multilingual.inc');
require(DRUPAL_ROOT . '/modules/field/field.attach.inc');
/**
@@ -67,6 +68,13 @@ require(DRUPAL_ROOT . '/modules/field/field.attach.inc');
define('FIELD_CARDINALITY_UNLIMITED', -1);
/**
+ * The language code assigned to untranslatable fields.
+ *
+ * Defined by ISO639-2 for "No linguistic content / Not applicable".
+ */
+define('FIELD_LANGUAGE_NONE', 'zxx');
+
+/**
* TODO
*/
define('FIELD_BEHAVIOR_NONE', 0x0001);
@@ -274,13 +282,15 @@ function field_associate_fields($module) {
* The field structure.
* @param $instance
* The instance structure.
+ * @param $langcode
+ * The field language to fill-in with the default value.
*/
-function field_get_default_value($obj_type, $object, $field, $instance) {
+function field_get_default_value($obj_type, $object, $field, $instance, $langcode = NULL) {
$items = array();
if (!empty($instance['default_value_function'])) {
$function = $instance['default_value_function'];
if (drupal_function_exists($function)) {
- $items = $function($obj_type, $object, $field, $instance);
+ $items = $function($obj_type, $object, $field, $instance, $langcode);
}
}
elseif (!empty($instance['default_value'])) {
@@ -699,6 +709,8 @@ function template_preprocess_field(&$variables) {
'label' => check_plain(t($instance['label'])),
'label_display' => $element['#label_display'],
'field_empty' => $field_empty,
+ 'field_language' => $element['#language'],
+ 'field_translatable' => $field['translatable'],
'template_files' => array(
'field',
'field-' . $element['#field_name'],
diff --git a/modules/field/field.multilingual.inc b/modules/field/field.multilingual.inc
new file mode 100644
index 000000000..9dafe23d6
--- /dev/null
+++ b/modules/field/field.multilingual.inc
@@ -0,0 +1,123 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Multilingual field API helper functions.
+ */
+
+/**
+ * Collect the available languages for the given entity type and field.
+ *
+ * If an entity has a translation handler and the given field is translatable,
+ * a (not necessarily strict) subset of the current enabled languages will be
+ * returned, otherwise only FIELD_LANGUAGE_NONE will be returned. Since the
+ * default value for a 'translatable' entity property is FALSE, we ensure that
+ * only entities that are able to handle translations actually get translatable
+ * fields.
+ *
+ * @param $obj_type
+ * The type of the entity the field is attached to, 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.
+ * @return
+ * An array of valid language codes.
+ */
+function field_multilingual_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]) || !empty($suggested_languages)) {
+ $obj_info = field_info_fieldable_types($obj_type);
+ if (!empty($obj_info['translation_handlers']) && $field['translatable']) {
+ $available_languages = field_multilingual_content_languages();
+ // 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);
+ }
+ // Accept only available languages.
+ $result = array_values(array_intersect($available_languages, $languages));
+ // Do not cache suggested values as they might alter the general result.
+ if (empty($suggested_languages)) {
+ $field_languages[$field_name] = $result;
+ }
+ }
+ else {
+ $result = $field_languages[$field_name] = array(FIELD_LANGUAGE_NONE);
+ }
+ }
+ else {
+ $result = $field_languages[$field_name];
+ }
+
+ return $result;
+}
+
+/**
+ * Return available content languages.
+ *
+ * The languages that may be associated to fields include FIELD_LANGAUGE_NONE.
+ *
+ * @return
+ * An array of language codes.
+ */
+function field_multilingual_content_languages() {
+ return array_keys(language_list() + array(FIELD_LANGUAGE_NONE => NULL));
+}
+
+
+/**
+ * Check if a module is registered as a translation handler for a given entity.
+ *
+ * @param $obj_type
+ * The type of the entity whose fields are to be translated.
+ * @param $handler
+ * The name of the handler to be checked.
+ * @return
+ * TRUE, if the handler is allowed to manage field translations.
+ */
+function field_multilingual_check_translation_handler($obj_type, $handler) {
+ $obj_info = field_info_fieldable_types($obj_type);
+ return isset($obj_info['translation_handlers'][$handler]);
+}
+
+/**
+ * Helper function to ensure that a given language code is valid.
+ *
+ * Checks whether the given language is one of the enabled languages. Otherwise,
+ * it returns the current, global language; or the site's default language, if
+ * the additional parameter $default is TRUE.
+ *
+ * @param $langcode
+ * The language code to validate.
+ * @param $default
+ * Whether to return the default language code or the current language code in
+ * case $langcode is invalid.
+ * @return
+ * A valid language code.
+ */
+function field_multilingual_valid_language($langcode, $default = TRUE) {
+ $enabled_languages = field_multilingual_content_languages();
+ if (in_array($langcode, $enabled_languages)) {
+ return $langcode;
+ }
+ // @todo Currently, node language neutral code is an empty string. Node passes
+ // $node->language as language parameter to field_attach_form(). We might
+ // want to unify the two "language neutral" language codes.
+ if ($langcode === '') {
+ return FIELD_LANGUAGE_NONE;
+ }
+ global $language;
+ $langcode = $default ? language_default('language') : $language->language;
+ if (in_array($langcode, $enabled_languages)) {
+ return $langcode;
+ }
+ // @todo Throw a more specific exception.
+ throw new FieldException('No valid content language could be found.');
+}
diff --git a/modules/field/field.test b/modules/field/field.test
index 49a1694bf..db498ae6e 100644
--- a/modules/field/field.test
+++ b/modules/field/field.test
@@ -59,6 +59,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// field_test_field_load() in field_test.module).
$this->instance['settings']['test_hook_field_load'] = TRUE;
field_update_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
$entity_type = 'test_entity';
$values = array();
@@ -73,12 +74,12 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode] = $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}[$langcode] = $values[$revision_id];
field_attach_update($entity_type, $revision[$revision_id]);
}
}
@@ -87,12 +88,12 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode]), $this->field['cardinality'], t('Current 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}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current 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}[$langcode][$delta]['additional_key'], 'additional_value', t('Current 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 DrupalWebTestCase {
$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}[$langcode]), $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}[$langcode][$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}[$langcode][$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 DrupalWebTestCase {
*/
function testFieldAttachLoadMultiple() {
$entity_type = 'test_entity';
+ $langcode = FIELD_LANGUAGE_NONE;
// Define 2 bundles.
$bundles = array(
@@ -158,7 +160,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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($langcode => array(array('value' => $values[$index][$field_name])));
}
field_attach_insert($entity_type, $entity);
}
@@ -169,17 +171,17 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode][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}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
}
}
// Check that the single-field load option works.
$entity = field_test_create_stub_entity(1, 1, $bundles[1]);
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
- $this->assertEqual($entity->{$field_names[1]}[0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1)));
- $this->assertEqual($entity->{$field_names[1]}[0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1)));
+ $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1)));
+ $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1)));
$this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
$this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
}
@@ -190,6 +192,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
function testFieldAttachSaveMissingData() {
$entity_type = 'test_entity';
$entity_init = field_test_create_stub_entity();
+ $langcode = FIELD_LANGUAGE_NONE;
// Insert: Field is missing.
$entity = clone($entity_init);
@@ -197,28 +200,28 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode]), 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}[$langcode] = 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}[$langcode]), 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}[$langcode] = $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}[$langcode], $values, t('Field data saved'));
// Update: Field is missing. Data should survive.
field_cache_clear();
@@ -227,17 +230,17 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode], $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}[$langcode] = 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}[$langcode]), t('Update: NULL field removes existing values'));
}
/**
@@ -250,15 +253,16 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$entity_type = 'test_entity';
$entity_init = field_test_create_stub_entity();
+ $langcode = FIELD_LANGUAGE_NONE;
// Insert: Field is NULL.
$entity = clone($entity_init);
- $entity->{$this->field_name} = NULL;
+ $entity->{$this->field_name}[$langcode] = 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}[$langcode]), t('Insert: NULL field results in no value saved'));
// Insert: Field is missing.
field_cache_clear();
@@ -268,7 +272,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode], $values, t('Insert: missing field results in default value saved'));
}
/**
@@ -276,6 +280,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
*/
function testFieldAttachQuery() {
$cardinality = $this->field['cardinality'];
+ $langcode = FIELD_LANGUAGE_NONE;
// Create an additional bundle with an instance of the field.
field_test_create_bundle('test_bundle_1', 'Test Bundle 1');
@@ -294,13 +299,13 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode][$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($langcode => array(array('value' => $common_value)));
field_attach_insert($entity_types[2], $entities[2]);
// Query on the object's values.
@@ -364,7 +369,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
for ($i = 0; $i < 20; ++$i) {
$offset_id += mt_rand(2, 5);
$offset_entities[$offset_id] = field_test_create_stub_entity($offset_id, $offset_id, 'offset_bundle');
- $offset_entities[$offset_id]->{$this->field_name}[0] = array('value' => $offset_id);
+ $offset_entities[$offset_id]->{$this->field_name}[$langcode][0] = array('value' => $offset_id);
field_attach_insert('test_entity', $offset_entities[$offset_id]);
}
@@ -397,19 +402,20 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// 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));
+ $langcode = FIELD_LANGUAGE_NONE;
$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}[$langcode][$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}[$langcode][0] = array('value' => $common_value);
field_attach_update($entity_type, $entities[2]);
// Query on the object's values.
@@ -452,10 +458,11 @@ class FieldAttachTestCase extends DrupalWebTestCase {
function testFieldAttachViewAndPreprocess() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
// Populate values to be displayed.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
- $entity->{$this->field_name} = $values;
+ $entity->{$this->field_name}[$langcode] = $values;
// Simple formatter, label displayed.
$formatter_setting = $this->randomName();
@@ -525,32 +532,45 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// TODO:
// - check display order with several fields
+
+ // Preprocess template.
+ $variables = array();
+ field_attach_preprocess($entity_type, $entity, $entity->content, $variables);
+ $result = TRUE;
+ foreach ($values as $delta => $item) {
+ if ($variables[$this->field_name][$delta]['value'] !== $item['value']) {
+ $result = FALSE;
+ break;
+ }
+ }
+ $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
}
function testFieldAttachDelete() {
$entity_type = 'test_entity';
+ $langcode = FIELD_LANGUAGE_NONE;
$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}[$langcode] = $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}[$langcode] = $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}[$langcode] = $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}[$langcode]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
}
// Delete revision 1, confirm the other two still load.
@@ -558,13 +578,13 @@ class FieldAttachTestCase extends DrupalWebTestCase {
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}[$langcode]), $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}[$langcode]), $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]);
@@ -590,15 +610,16 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Save an object with data in the field.
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
- $entity->{$this->field_name} = $values;
+ $entity->{$this->field_name}[$langcode] = $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}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle");
// Rename the bundle. This has to be initiated by the module so that its
// hook_fieldable_info() is consistent.
@@ -612,7 +633,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// 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}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage");
}
function testFieldAttachDeleteBundle() {
@@ -644,17 +665,18 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Save an object with data for both fields
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
- $entity->{$this->field_name} = $values;
- $entity->{$field_name} = $this->_generateTestFieldValues(1);
+ $entity->{$this->field_name}[$langcode] = $values;
+ $entity->{$field_name}[$langcode] = $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}[$langcode]), 4, 'First field got loaded');
+ $this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded');
// Delete the bundle. This has to be initiated by the module so that its
// hook_fieldable_info() is consistent.
@@ -663,8 +685,8 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// 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}[$langcode]), 'No data for first field');
+ $this->assertFalse(isset($entity->{$field_name}[$langcode]), '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");
@@ -677,6 +699,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
function testFieldAttachCache() {
// Initialize random values and a test entity.
$entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$noncached_type = 'test_entity';
@@ -691,7 +714,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
- $entity->{$this->field_name} = $values;
+ $entity->{$this->field_name}[$langcode] = $values;
field_attach_insert($noncached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert'));
@@ -709,7 +732,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Save, and check that no cache entry is present.
$entity = clone($entity_init);
- $entity->{$this->field_name} = $values;
+ $entity->{$this->field_name}[$langcode] = $values;
field_attach_insert($cached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert'));
@@ -723,12 +746,12 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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][$langcode], $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}[$langcode] = $values;
field_attach_update($cached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update'));
@@ -736,13 +759,13 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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][$langcode], $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}[$langcode] = $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'));
@@ -751,7 +774,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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][$langcode], $values, t('Cached: correct cache entry on load'));
// Delete, and check that the cache entry is wiped.
field_attach_delete($cached_type, $entity);
@@ -763,6 +786,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
function testFieldAttachValidate() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
// Set up values to generate errors
$values = array();
@@ -772,7 +796,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
}
// Arrange for item 1 not to generate an error
$values[1]['value'] = 1;
- $entity->{$this->field_name} = $values;
+ $entity->{$this->field_name}[$langcode] = $values;
try {
field_attach_validate($entity_type, $entity);
@@ -783,15 +807,15 @@ class FieldAttachTestCase extends DrupalWebTestCase {
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][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
+ $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta");
+ unset($errors[$this->field_name][$langcode][$delta]);
}
else {
- $this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta");
+ $this->assertFalse(isset($errors[$this->field_name][$langcode][$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][$langcode]), 0, 'No extraneous errors set');
}
// Validate that FAPI elements are generated. This could be much
@@ -803,10 +827,11 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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']}");
+ $langcode = FIELD_LANGUAGE_NONE;
+ $this->assertEqual($form[$this->field_name][$langcode]['#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][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
}
}
@@ -833,7 +858,8 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Leave an empty value. 'field_test' fields are empty if empty().
$values[1]['value'] = 0;
- $form_state['values'] = array($this->field_name => $values);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $form_state['values'] = array($this->field_name => array($langcode => $values));
field_attach_submit($entity_type, $entity, $form, $form_state);
asort($weights);
@@ -843,7 +869,7 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$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}[$langcode], $expected_values, 'Submit filters empty values');
}
/**
@@ -1102,45 +1128,46 @@ class FieldFormTestCase extends DrupalWebTestCase {
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// 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}[$langcode][0][value]", '', 'Widget is displayed');
+ $this->assertNoField("{$this->field_name}[$langcode][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}[$langcode][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}[$langcode][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}[$langcode][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}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value');
+ $this->assertNoField("{$this->field_name}[$langcode][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}[$langcode][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}[$langcode][0]['value'], $value, 'Field value was updated');
// Empty the field.
$value = '';
- $edit = array($this->field_name . '[0][value]' => $value);
+ $edit = array("{$this->field_name}[$langcode][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);
@@ -1155,6 +1182,7 @@ class FieldFormTestCase extends DrupalWebTestCase {
$this->instance['required'] = TRUE;
field_create_field($this->field);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// Submit with missing required value.
$edit = array();
@@ -1163,17 +1191,17 @@ class FieldFormTestCase extends DrupalWebTestCase {
// Create an entity
$value = mt_rand(1, 127);
- $edit = array($this->field_name . '[0][value]' => $value);
+ $edit = array("{$this->field_name}[$langcode][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}[$langcode][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}[$langcode][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');
}
@@ -1192,17 +1220,18 @@ class FieldFormTestCase extends DrupalWebTestCase {
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// 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}[$langcode][0][value]", '', 'Widget 1 is displayed');
+ $this->assertNoField("{$this->field_name}[$langcode][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}[$langcode][0][value]", '', 'Widget 1 is displayed');
+ $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed');
+ $this->assertNoField("{$this->field_name}[$langcode][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.
@@ -1219,8 +1248,8 @@ class FieldFormTestCase extends DrupalWebTestCase {
} 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[$langcode][$delta][value]"] = $value;
+ $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight;
// We'll need three slightly different formats to check the values.
$values[$weight] = $value;
$field_values[$weight]['value'] = (string)$value;
@@ -1232,15 +1261,15 @@ class FieldFormTestCase extends DrupalWebTestCase {
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[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
+ $this->assertFieldByName("$this->field_name[$langcode][$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[$langcode][$delta][value]", '', "New widget is displayed");
+ $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight");
+ $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
// Submit the form and create the entity.
$this->drupalPost(NULL, $edit, t('Save'));
@@ -1250,7 +1279,7 @@ class FieldFormTestCase extends DrupalWebTestCase {
$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}[$langcode], $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
@@ -1272,6 +1301,7 @@ class FieldFormTestCase extends DrupalWebTestCase {
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// Display creation form -> 1 widget.
$this->drupalGet('test-entity/add/test-bundle');
@@ -1293,8 +1323,8 @@ class FieldFormTestCase extends DrupalWebTestCase {
} 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[$langcode][$delta][value]"] = $value;
+ $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight;
// We'll need three slightly different formats to check the values.
$values[$weight] = $value;
$field_values[$weight]['value'] = (string)$value;
@@ -1307,15 +1337,15 @@ class FieldFormTestCase extends DrupalWebTestCase {
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[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
+ $this->assertFieldByName("$this->field_name[$langcode][$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[$langcode][$delta][value]", '', "New widget is displayed");
+ $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight");
+ $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
}
/**
@@ -1599,17 +1629,18 @@ class FieldCrudTestCase extends DrupalWebTestCase {
// Save an object with data for the field
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
$values[0]['value'] = mt_rand(1, 127);
- $entity->{$field['field_name']} = $values;
+ $entity->{$field['field_name']}[$langcode] = $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']}[$langcode]), 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']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
}
}
}
@@ -1800,6 +1831,236 @@ class FieldInstanceCrudTestCase extends DrupalWebTestCase {
}
/**
+ * 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_multilingual_available_languages().
+ */
+class FieldTranslationsTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Field translations tests',
+ 'description' => 'Test multilanguage fields logic.',
+ 'group' => '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);
+
+ for ($i = 0; $i < 3; ++$i) {
+ locale_inc_callback('locale_add_language', 'l' . $i, $this->randomString(), $this->randomString());
+ }
+ }
+
+ /**
+ * Ensure that only valid values are returned by field_multilingual_available_languages().
+ */
+ function testFieldAvailableLanguages() {
+ // Test 'translatable' fieldable info.
+ $field = $this->field;
+ $field['field_name'] .= '_untranslatable';
+ $langcode = language_default();
+ $suggested_languages = array($langcode->language);
+ $available_languages = field_multilingual_available_languages($this->obj_type, $field);
+ $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NONE, t('Untranslatable entity: suggested language ignored.'));
+
+ // Enable field translations for the entity.
+ field_test_fieldable_info_translatable('test_entity', 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_multilingual_available_languages($this->obj_type, $this->field);
+ $this->assertTrue(in_array(FIELD_LANGUAGE_NONE, $available_languages), t('%language is an available language.', array('%language' => FIELD_LANGUAGE_NONE)));
+ foreach ($available_languages as $delta => $langcode) {
+ if ($langcode != FIELD_LANGUAGE_NONE) {
+ $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode)));
+ }
+ }
+ $this->assertFalse(in_array('xx', $available_languages), t('No invalid language was made available.'));
+ $this->assertTrue(count($available_languages) == count($enabled_languages), t('An enabled language was successfully made unavailable.'));
+
+ // Test field_multilingual_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_multilingual_available_languages($this->obj_type, $this->field);
+ $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NONE, 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 {
+ $langcode = $enabled_languages[mt_rand(0, $lang_count)];
+ }
+ while (in_array($langcode, $suggested_languages));
+ $suggested_languages[] = $langcode;
+ }
+
+ $available_languages = field_multilingual_available_languages($this->obj_type, $this->field, $suggested_languages);
+ $this->assertEqual(count($available_languages), count($suggested_languages), t('Suggested languages were successfully made available.'));
+ foreach ($available_languages as $langcode) {
+ $this->assertTrue(in_array($langcode, $available_languages), t('Suggested language %language is available.', array('%language' => $langcode)));
+ }
+
+ $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
+ $suggested_languages = array('xx');
+ $available_languages = field_multilingual_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_multilingual_available_languages().
+ $values = array();
+ $extra_languages = mt_rand(1, 4);
+ $languages = $available_languages = field_multilingual_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 $langcode) {
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$langcode][$delta]['value'] = mt_rand(1, 127);
+ }
+ }
+ $entity->{$this->field_name} = $values;
+
+ $results = _field_invoke('test_op', $entity_type, $entity);
+ foreach ($results as $langcode => $result) {
+ $hash = md5(serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode])));
+ // Check whether 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' => $langcode)));
+ }
+ $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_multilingual_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 whether _field_invoke()
+ // correctly uses the result of field_multilingual_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 $langcode) {
+ for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+ $values[$id][$langcode][$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 $langcode => $result) {
+ $hash = md5(serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode])));
+ // Check whether 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' => $langcode)));
+ }
+ $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for object %id.', array('%id' => $id)));
+ }
+ }
+
+ /**
+ * Test translatable fields storage/retrieval.
+ */
+ function testTranslatableFieldSaveLoad() {
+ // Enable field translations for nodes.
+ field_test_fieldable_info_translatable('node', TRUE);
+ $obj_info = field_info_fieldable_types('node');
+ $this->assertTrue(count($obj_info['translation_handlers']), t('Nodes are translatable.'));
+
+ // Prepare the field translations.
+ $eid = $evid = 1;
+ $obj_type = 'test_entity';
+ $object = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
+ $field_translations = array();
+ foreach (field_multilingual_available_languages($obj_type, $this->field) as $langcode) {
+ $field_translations[$langcode] = FieldAttachTestCase::_generateTestFieldValues($this->field['cardinality']);
+ }
+
+ // Save and reload the field translations.
+ $object->{$this->field_name} = $field_translations;
+ field_attach_insert($obj_type, $object);
+ unset($object->{$this->field_name});
+ field_attach_load($obj_type, array($eid => $object));
+
+ // Check if the correct values were saved/loaded.
+ foreach ($field_translations as $langcode => $items) {
+ $result = TRUE;
+ foreach ($items as $delta => $item) {
+ $result = $result && $item['value'] == $object->{$this->field_name}[$langcode][$delta]['value'];
+ }
+ $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode)));
+ }
+ }
+}
+
+/**
* Unit test class for field bulk delete and batch purge functionality.
*/
class FieldBulkDeleteTestCase extends DrupalWebTestCase {
@@ -1898,7 +2159,7 @@ class FieldBulkDeleteTestCase extends DrupalWebTestCase {
for ($i = 0; $i < 10; $i++) {
$entity = field_test_create_stub_entity($id, $id, $bundle);
foreach ($this->fields as $field) {
- $entity->{$field['field_name']} = $this->_generateTestFieldValues($field['cardinality']);
+ $entity->{$field['field_name']}[FIELD_LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']);
}
$this->entities[$id] = $entity;
field_attach_insert($this->entity_type, $entity);
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index db4c5fcdd..43a9414aa 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -144,8 +144,16 @@ function _field_sql_storage_schema($field) {
'not null' => TRUE,
'description' => 'The sequence number for this data item, used for multi-value fields',
),
+ // @todo Consider storing language as integer.
+ '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($field) {
$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,
@@ -224,7 +232,7 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
if (!isset($skip_fields[$instance['field_id']]) && (!isset($options['field_id']) || $options['field_id'] == $instance['field_id'])) {
$objects[$id]->{$field_name} = array();
$field_ids[$instance['field_id']][] = $load_current ? $id : $vid;
- $delta_count[$id][$field_name] = 0;
+ $delta_count[$id][$field_name] = array();
}
}
}
@@ -238,6 +246,7 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
->fields('t')
->condition('etid', $etid)
->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
+ ->condition('language', field_multilingual_available_languages($obj_type, $field), 'IN')
->orderBy('delta');
if (empty($options['deleted'])) {
@@ -247,7 +256,11 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
$results = $query->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.
@@ -257,8 +270,8 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
}
// 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]++;
}
}
}
@@ -288,17 +301,33 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi
// 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_multilingual_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, 'IN')
+ ->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, 'IN')
+ ->execute();
}
}
- if ($object->$field_name) {
+ if (!empty($available_translations)) {
// Prepare the multi-insert query.
- $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta');
+ $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
foreach ($field['columns'] as $column => $attributes) {
$columns[] = _field_sql_storage_columnname($field_name, $column);
}
@@ -307,25 +336,30 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi
$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 $langcode) {
+ if ($items = $object->{$field_name}[$langcode]) {
+ $delta_count = 0;
+ foreach ($items as $delta => $item) {
+ $record = array(
+ 'etid' => $etid,
+ 'entity_id' => $id,
+ 'revision_id' => $vid,
+ 'bundle' => $bundle,
+ 'delta' => $delta,
+ 'language' => $langcode,
+ );
+ 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;
+ }
+ }
}
}
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test
index c35ea76a2..cb4aee045 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.test
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.test
@@ -56,9 +56,10 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
function testFieldAttachLoad() {
$entity_type = 'test_entity';
$eid = 0;
+ $langcode = FIELD_LANGUAGE_NONE;
$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 DrupalWebTestCase {
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, $langcode, $value));
}
}
$query->execute();
@@ -76,7 +77,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
// 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, $langcode, $value));
}
$query->execute();
@@ -85,10 +86,10 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
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}[$langcode][$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}[$langcode]), "No extraneous value gets loaded for current revision.");
}
}
@@ -98,13 +99,23 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
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}[$langcode][$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}[$langcode]), "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 DrupalWebTestCase {
function testFieldAttachInsertAndUpdate() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
// Test insert.
$values = array();
@@ -122,7 +134,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
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}[$langcode] = $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 DrupalWebTestCase {
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}[$langcode] = $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 DrupalWebTestCase {
}
$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}[$langcode]);
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 DrupalWebTestCase {
}
// Check that update with an empty $object->$field_name empties the field.
- $entity->{$this->field_name} = NULL;
+ $entity->{$this->field_name}[$langcode] = 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,6 +206,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
function testFieldAttachSaveMissingData() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $langcode = FIELD_LANGUAGE_NONE;
// Insert: Field is missing
field_attach_insert($entity_type, $entity);
@@ -204,7 +217,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertEqual($count, 0, 'Missing field results in no inserts');
// Insert: Field is NULL
- $entity->{$this->field_name} = NULL;
+ $entity->{$this->field_name}[$langcode] = NULL;
field_attach_insert($entity_type, $entity);
$count = db_select($this->table)
->countQuery()
@@ -213,7 +226,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertEqual($count, 0, 'NULL field results in no inserts');
// Add some real data
- $entity->{$this->field_name} = array(0 => array('value' => 1));
+ $entity->{$this->field_name}[$langcode] = array(0 => array('value' => 1));
field_attach_insert($entity_type, $entity);
$count = db_select($this->table)
->countQuery()
@@ -238,5 +251,47 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
->execute()
->fetchField();
$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_select($this->table)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $this->assertEqual($count, 1, 'Field translation in an unavailable language saved.');
+
+ // Again add some real data.
+ $entity->{$this->field_name}[$langcode] = array(0 => array('value' => 1));
+ field_attach_insert($entity_type, $entity);
+ $count = db_select($this->table)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $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}[$langcode]);
+ field_attach_update($entity_type, $entity);
+ $count = db_select($this->table)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $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}[$langcode] = NULL;
+ field_attach_update($entity_type, $entity);
+ $count = db_select($this->table)
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+ $this->assertEqual($count, 1, 'NULL field translation is wiped.');
}
}
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index f7401b837..ad1335df0 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -228,12 +228,12 @@ function list_allowed_values_validate($element, &$form_state) {
* 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, $langcode, $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']][$langcode][$delta][] = array(
'error' => 'list_illegal_value',
'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
);
diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module
index 52b3685f3..b956a9e77 100644
--- a/modules/field/modules/number/number.module
+++ b/modules/field/modules/number/number.module
@@ -170,17 +170,17 @@ function number_field_instance_settings_form($field, $instance) {
* - '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, $langcode, $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']][$langcode][$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']][$langcode][$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'])),
);
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index 71ba9768e..b1d287e32 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -180,7 +180,7 @@ function text_field_instance_settings_form($field, $instance) {
* - '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, $langcode, $items, &$errors) {
foreach ($items as $delta => $item) {
foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) {
if (!empty($item[$column])) {
@@ -193,7 +193,7 @@ function text_field_validate($obj_type, $object, $field, $instance, $items, &$er
$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']][$langcode][$delta][] = array(
'error' => "text_{$column}_length",
'message' => $message,
);
@@ -211,9 +211,7 @@ function text_field_validate($obj_type, $object, $field, $instance, $items, &$er
* 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, $langcode, &$items) {
foreach ($objects as $id => $object) {
foreach ($items[$id] as $delta => $item) {
if (!empty($instances[$id]['settings']['text_processing'])) {
@@ -221,10 +219,9 @@ function text_field_load($obj_type, $objects, $field, $instances, &$items) {
// 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) : '';
+ $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode, FALSE) : '';
if ($field['type'] == 'text_with_summary') {
- $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, FALSE) : '';
+ $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode, FALSE) : '';
}
}
}
@@ -243,8 +240,7 @@ function text_field_load($obj_type, $objects, $field, $instances, &$items) {
*
* @see text_field_load()
*/
-function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
- global $language;
+function text_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
// Only sanitize items which were not already processed inside
// text_field_load(), i.e. items with uncacheable text formats, or coming
@@ -252,10 +248,9 @@ function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
if (!isset($items[$delta]['safe'])) {
if (!empty($instance['settings']['text_processing'])) {
$format = $item['format'];
- $lang = isset($object->language) ? $object->language : $language->language;
- $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang) : '';
+ $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $langcode) : '';
if ($field['type'] == 'text_with_summary') {
- $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang) : '';
+ $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $langcode) : '';
}
}
else {
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index db7c96c7c..67a8f5497 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -50,8 +50,9 @@ class TextFieldTestCase extends DrupalWebTestCase {
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);
+ $langcode = FIELD_LANGUAGE_NONE;
for ($i = 0; $i <= $max_length + 2; $i++) {
- $entity->{$this->field['field_name']}[0]['value'] = str_repeat('x', $i);
+ $entity->{$this->field['field_name']}[$langcode][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 DrupalWebTestCase {
)
);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// 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}[$langcode][0][value]", '', t('Widget is displayed'));
+ $this->assertNoFieldByName("{$this->field_name}[$langcode][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}[$langcode][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 DrupalWebTestCase {
)
);
field_create_instance($this->instance);
+ $langcode = FIELD_LANGUAGE_NONE;
// 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}[$langcode][0][value]", '', t('Widget is displayed'));
+ $this->assertNoFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is not displayed'));
// Submit with data that should be filtered.
$value = $this->randomName() . '<br />' . $this->randomName();
$edit = array(
- $this->field_name . '[0][value]' => $value,
+ "{$this->field_name}[$langcode][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 DrupalWebTestCase {
// 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}[$langcode][0][value]", '', t('Widget is displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][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}[$langcode][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'));
diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php
index efa629da1..d58ff1637 100644
--- a/modules/field/theme/field.tpl.php
+++ b/modules/field/theme/field.tpl.php
@@ -18,6 +18,8 @@
* - $label: The item label.
* - $label_display: Position of label display, inline, above, or hidden.
* - $field_empty: Whether the field has any valid value.
+ * - $field_language: The field language.
+ * - $field_translatable: Whether the field is translatable or not.
*
* Each $item in $items contains:
* - 'view' - the themed view for that item
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index 02ce61b07..45d36e857 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -1207,7 +1207,7 @@ function field_ui_default_value_widget($field, $instance, &$form, &$form_state)
);
drupal_function_exists('field_default_form');
// @todo Allow multiple values (requires more work on 'add more' JS handler).
- $widget_form = field_default_form(NULL, NULL, $field, $instance, $items, $form, $form_state, 0);
+ $widget_form = field_default_form(NULL, NULL, $field, $instance, FIELD_LANGUAGE_NONE, $items, $form, $form_state, 0);
$form['instance']['default_value_widget'] += $widget_form;
$form['#fields'][$field['field_name']]['form_path'] = array(
'instance',
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index 9ee175a26..af74fcfa2 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -111,8 +111,9 @@ class FilterAdminTestCase extends DrupalWebTestCase {
$edit = array();
$edit['title'] = $this->randomName();
- $edit['body[0][value]'] = $body . '<random>' . $extra_text . '</random>';
- $edit['body[0][value_format]'] = $filtered;
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $body . '<random>' . $extra_text . '</random>';
+ $edit["body[$langcode][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.'));
diff --git a/modules/forum/forum.test b/modules/forum/forum.test
index f3137edac..937d8e2db 100644
--- a/modules/forum/forum.test
+++ b/modules/forum/forum.test
@@ -232,9 +232,10 @@ class ForumTestCase extends DrupalWebTestCase {
$body = $this->randomName(200);
$tid = $forum['tid']; // Without this being set, post variable equals the first non-blank in select items list.
+ $langcode = FIELD_LANGUAGE_NONE;
$edit = array(
'title' => $title,
- 'body[0][value]' => $body,
+ "body[$langcode][0][value]" => $body,
'taxonomy[1]' => $tid
);
@@ -321,7 +322,8 @@ class ForumTestCase extends DrupalWebTestCase {
// Edit forum node (including moving it to another forum).
$edit = array();
$edit['title'] = 'node/' . $node->nid;
- $edit['body[0][value]'] = $this->randomName(256);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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'));
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index d2a7d3315..e2bc0e62e 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -1389,7 +1389,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase {
$edit = array(
'type' => 'page',
'title' => $node_title,
- 'body' => array(array('value' => $node_body)),
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array('value' => $node_body))),
'language' => $langcode,
);
$node = $this->drupalCreateNode($edit);
@@ -1483,6 +1483,7 @@ class UILanguageNegotiationTest extends DrupalWebTestCase {
// into database when seen by t(). Without doing this, our target string
// is for some reason not found when doing translate search. This might
// be some bug.
+ drupal_static_reset('language_list');
$languages = language_list('enabled');
variable_set('language_default', $languages[1]['vi']);
// First visit this page to make sure our target string is searchable.
diff --git a/modules/node/node.install b/modules/node/node.install
index ddb571ebd..2c7e6f843 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -511,15 +511,15 @@ function node_update_7006(&$context) {
'type' => $revision->type,
);
if (!empty($revision->teaser) && $revision->teaser != text_summary($revision->body)) {
- $node->body[0]['summary'] = $revision->teaser;
+ $node->body[FIELD_LANGUAGE_NONE][0]['summary'] = $revision->teaser;
}
// Do this after text_summary() above.
$break = '<!--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_NONE][0]['value'] = $revision->body;
+ $node->body[FIELD_LANGUAGE_NONE][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());
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 2c1b942d6..df66459e6 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -285,7 +285,7 @@ function node_form(&$form_state, $node) {
$form['#theme'] = array($node->type . '_node_form', 'node_form');
$form['#builder_function'] = 'node_form_submit_build_node';
- field_attach_form('node', $node, $form, $form_state);
+ field_attach_form('node', $node, $form, $form_state, $node->language);
return $form;
}
diff --git a/modules/node/node.test b/modules/node/node.test
index b567ba77d..8e771282f 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -141,7 +141,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase {
// 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_NONE][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 DrupalWebTestCase {
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_NONE][0]['value'] == $reverted_node->body[FIELD_LANGUAGE_NONE][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,8 @@ class PageEditTestCase extends DrupalWebTestCase {
* Check node edit functionality.
*/
function testPageEdit() {
- $body_key = 'body[0][value]';
+ $langcode = FIELD_LANGUAGE_NONE;
+ $body_key = "body[$langcode][0][value]";
// Create node to edit.
$edit = array();
$edit['title'] = $this->randomName(8);
@@ -241,7 +242,8 @@ class PagePreviewTestCase extends DrupalWebTestCase {
* Check the node preview functionality.
*/
function testPagePreview() {
- $body_key = 'body[0][value]';
+ $langcode = FIELD_LANGUAGE_NONE;
+ $body_key = "body[$langcode][0][value]";
// Fill in node creation form and preview node.
$edit = array();
@@ -263,7 +265,8 @@ class PagePreviewTestCase extends DrupalWebTestCase {
* Check the node preview functionality, when using revisions.
*/
function testPagePreviewWithRevisions() {
- $body_key = 'body[0][value]';
+ $langcode = FIELD_LANGUAGE_NONE;
+ $body_key = "body[$langcode][0][value]";
// Force revision on page content.
variable_set('node_options_page', array('status', 'revision'));
@@ -311,7 +314,8 @@ class PageCreationTestCase extends DrupalWebTestCase {
// Create a node.
$edit = array();
$edit['title'] = $this->randomName(8);
- $edit['body[0][value]'] = $this->randomName(16);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
$this->drupalPost('node/add/page', $edit, t('Save'));
// Check that the page has been created.
@@ -377,7 +381,7 @@ class SummaryLengthTestCase extends DrupalWebTestCase {
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_NONE => 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 +504,8 @@ class NodePostSettingsTestCase extends DrupalWebTestCase {
// Create a node.
$edit = array();
$edit['title'] = $this->randomName(8);
- $edit['body[0][value]'] = $this->randomName(16);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
$this->drupalPost('node/add/page', $edit, t('Save'));
// Check that the post information is displayed.
@@ -521,7 +526,8 @@ class NodePostSettingsTestCase extends DrupalWebTestCase {
// Create a node.
$edit = array();
$edit['title'] = $this->randomName(8);
- $edit['body[0][value]'] = $this->randomName(16);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $this->randomName(16);
$this->drupalPost('node/add/page', $edit, t('Save'));
// Check that the post information is displayed.
@@ -764,7 +770,7 @@ class NodeSaveTestCase extends DrupalWebTestCase {
$title = $this->randomName(8);
$node = array(
'title' => $title,
- 'body' => array(array('value' => $this->randomName(32))),
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => $this->web_user->uid,
'type' => 'article',
'nid' => $test_nid,
diff --git a/modules/node/node.tpl.php b/modules/node/node.tpl.php
index 8aa31ca04..6ca204a5d 100644
--- a/modules/node/node.tpl.php
+++ b/modules/node/node.tpl.php
@@ -59,6 +59,13 @@
* - $logged_in: Flags true when the current user is a logged-in member.
* - $is_admin: Flags true when the current user is an administrator.
*
+ * Field variables: for each field instance attached to the node a corresponding
+ * variable is defined, e.g. $node->body becomes $body. When needing to access
+ * a field's raw values, developers/themers are strongly encouraged to use these
+ * variables. Otherwise they will have to explicitly specify the desired field
+ * language, e.g. $node->body['en'], thus overriding any language negotiation
+ * rule that was previously applied.
+ *
* @see template_preprocess()
* @see template_preprocess_node()
* @see template_process()
diff --git a/modules/path/path.test b/modules/path/path.test
index 41476c306..c80b56e21 100644
--- a/modules/path/path.test
+++ b/modules/path/path.test
@@ -180,6 +180,7 @@ class PathLanguageTestCase extends DrupalWebTestCase {
$edit['langcode'] = 'fr';
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+ drupal_static_reset('language_list');
// Set language negotiation to "Path prefix with fallback".
variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH);
@@ -212,7 +213,8 @@ class PathLanguageTestCase extends DrupalWebTestCase {
$this->clickLink(t('add translation'));
$edit = array();
$edit['title'] = $this->randomName();
- $edit['body[0][value]'] = $this->randomName();
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $this->randomName();
$edit['path'] = $this->randomName();
$this->drupalPost(NULL, $edit, t('Save'));
diff --git a/modules/php/php.test b/modules/php/php.test
index b7b48f6fc..6bd12ca8d 100644
--- a/modules/php/php.test
+++ b/modules/php/php.test
@@ -23,7 +23,7 @@ class PHPTestCase extends DrupalWebTestCase {
* @return stdObject Node object.
*/
function createNodeWithCode() {
- return $this->drupalCreateNode(array('body' => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))));
+ return $this->drupalCreateNode(array('body' => array(FIELD_LANGUAGE_NONE => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>')))));
}
}
@@ -60,7 +60,8 @@ class PHPFilterTestCase extends PHPTestCase {
// Change filter to PHP filter and see that PHP code is evaluated.
$edit = array();
- $edit['body[0][value_format]'] = 3;
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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.'));
diff --git a/modules/search/search.test b/modules/search/search.test
index 51fd33179..329937dd0 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -312,7 +312,7 @@ class SearchRankingTestCase extends DrupalWebTestCase {
// 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_NONE => 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 DrupalWebTestCase {
$settings[$node_rank] = 1;
break;
case 'relevance':
- $settings['body'][0]['value'] .= " really rocks";
+ $settings['body'][FIELD_LANGUAGE_NONE][0]['value'] .= " really rocks";
break;
case 'recent':
$settings['created'] = REQUEST_TIME + 3600;
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 893c7e8c6..c23081467 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -693,7 +693,7 @@ class DrupalWebTestCase extends DrupalTestCase {
protected function drupalCreateNode($settings = array()) {
// Populate defaults array.
$settings += array(
- 'body' => array(array()),
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array())),
'title' => $this->randomName(8),
'comment' => 2,
'changed' => REQUEST_TIME,
@@ -730,7 +730,7 @@ class DrupalWebTestCase extends DrupalTestCase {
'value' => $this->randomName(32),
'format' => FILTER_FORMAT_DEFAULT
);
- $settings['body'][0] += $body;
+ $settings['body'][FIELD_LANGUAGE_NONE][0] += $body;
$node = (object) $settings;
node_save($node);
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index ce9b0ade9..3a5036565 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -374,7 +374,7 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
// 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!') . "<?php drupal_add_css('$css', 'inline'); ?>", 'format' => 3)), // PHP filter.
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array('value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>", 'format' => 3))), // PHP filter.
'promote' => 1,
);
$node = $this->drupalCreateNode($settings);
diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module
index 75dee80de..8192280ea 100644
--- a/modules/simpletest/tests/field_test.module
+++ b/modules/simpletest/tests/field_test.module
@@ -88,6 +88,15 @@ function field_test_fieldable_info() {
}
/**
+ * Implement hook_fieldable_info_alter().
+ */
+function field_test_fieldable_info_alter(&$info) {
+ foreach (field_test_fieldable_info_translatable() as $obj_type => $translatable) {
+ $info[$obj_type]['translation_handlers']['field_test'] = TRUE;
+ }
+}
+
+/**
* Create a new bundle for test_entity objects.
*
* @param $bundle_name
@@ -372,7 +381,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, $langcode, &$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.
@@ -393,10 +402,10 @@ function field_test_field_load($obj_type, $objects, $field, $instances, &$items,
* 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, $langcode, $items, &$errors) {
foreach ($items as $delta => $item) {
if ($item['value'] == -1) {
- $errors[$field['field_name']][$delta][] = array(
+ $errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'field_test_invalid',
'message' => t('%name does not accept the value -1.', array('%name' => $instance['label'])),
);
@@ -407,7 +416,7 @@ function field_test_field_validate($obj_type, $object, $field, $instance, $items
/**
* 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, $langcode, &$items) {
foreach ($items as $delta => $item) {
$value = check_plain($item['value']);
$items[$delta]['safe'] = $value;
@@ -474,8 +483,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 +586,48 @@ function field_test_default_value($obj_type, $object, $field, $instance) {
}
/**
+ * Generic op to test _field_invoke behavior.
+ */
+function field_test_field_test_op($obj_type, $object, $field, $instance, $langcode, &$items) {
+ return array($langcode => md5(serialize(array($obj_type, $object, $field['field_name'], $langcode, $items))));
+}
+
+/**
+ * Generic op to test _field_invoke_multiple behavior.
+ */
+function field_test_field_test_op_multiple($obj_type, $objects, $field, $instances, $langcode, &$items) {
+ $result = array();
+ foreach ($objects as $id => $object) {
+ $result[$id] = array($langcode => md5(serialize(array($obj_type, $object, $field['field_name'], $langcode, $items[$id]))));
+ }
+ return $result;
+}
+
+/**
+ * Implement 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]);
+ }
+}
+
+/**
+ * Helper function to enable entity translations.
+ */
+function field_test_fieldable_info_translatable($obj_type = NULL, $translatable = NULL) {
+ $stored_value = &drupal_static(__FUNCTION__, array());
+ if (isset($obj_type) && isset($translatable)) {
+ $stored_value[$obj_type] = $translatable;
+ _field_info_collate_types(TRUE);
+ }
+ return $stored_value;
+}
+
+/**
* Store and retrieve keyed data for later verification by unit tests.
*
* This function is a simple in-memory key-value store with the
diff --git a/modules/system/system.test b/modules/system/system.test
index 491fbc8fe..2c642adff 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -495,7 +495,7 @@ class AccessDeniedTestCase extends DrupalWebTestCase {
$edit = array(
'title' => $this->randomName(10),
- 'body' => array(array('value' => $this->randomName(100))),
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
);
$node = $this->drupalCreateNode($edit);
@@ -572,7 +572,7 @@ class PageNotFoundTestCase extends DrupalWebTestCase {
$edit = array(
'title' => $this->randomName(10),
- 'body' => array(array('value' => $this->randomName(100))),
+ 'body' => array(FIELD_LANGUAGE_NONE => array(array('value' => $this->randomName(100)))),
);
$node = $this->drupalCreateNode($edit);
@@ -696,9 +696,10 @@ class PageTitleFiltering extends DrupalWebTestCase {
drupal_set_title($title, PASS_THROUGH);
$this->assertTrue(strpos(drupal_get_title(), '<em>') !== FALSE, t('Tags in title are not converted to entities when $output is PASS_THROUGH.'));
// Generate node content.
+ $langcode = FIELD_LANGUAGE_NONE;
$edit = array(
'title' => '!SimpleTest! ' . $title . $this->randomName(20),
- 'body[0][value]' => '!SimpleTest! test body' . $this->randomName(200),
+ "body[$langcode][0][value]" => '!SimpleTest! test body' . $this->randomName(200),
);
// Create the node with HTML in the title.
$this->drupalPost('node/add/page', $edit, t('Save'));
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 6294cf131..36459f73b 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -1930,7 +1930,7 @@ function taxonomy_field_schema($field) {
* Possible error codes:
* - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values.
*/
-function taxonomy_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
+function taxonomy_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
$allowed_values = taxonomy_allowed_values($field);
$widget = field_info_widget_types($instance['widget']['type']);
@@ -2032,7 +2032,7 @@ function taxonomy_allowed_values($field) {
* This preloads all taxonomy terms for multiple loaded objects at once and
* unsets values for invalid terms that do not exist.
*/
-function taxonomy_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
+function taxonomy_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
$tids = array();
// Collect every possible term attached to any of the fieldable entities.
diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test
index 3d20928a3..9ca9592d6 100644
--- a/modules/taxonomy/taxonomy.test
+++ b/modules/taxonomy/taxonomy.test
@@ -453,7 +453,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
// Post an article.
$edit = array();
$edit['title'] = $this->randomName();
- $edit['body[0][value]'] = $this->randomName();
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $this->randomName();
$edit['taxonomy[' . $this->vocabulary->vid . ']'] = $term1->tid;
$this->drupalPost('node/add/article', $edit, t('Save'));
@@ -495,7 +496,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
// 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();
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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) {
@@ -763,20 +765,21 @@ class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase {
field_create_instance($this->instance);
// Test valid and invalid values with field_attach_validate().
+ $langcode = FIELD_LANGUAGE_NONE;
$entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
$term = $this->createTerm($this->vocabulary);
- $entity->{$this->field_name}[0]['value'] = $term->tid;
+ $entity->{$this->field_name}[$langcode][0]['value'] = $term->tid;
field_attach_validate('test_entity', $entity);
try {
- $this->assertTrue($entity->{$this->field_name}[0]['value'] == $term->tid, t('Correct term does not cause validation error'));
+ $this->assertTrue($entity->{$this->field_name}[$langcode][0]['value'] == $term->tid, t('Correct term does not cause validation error'));
}
catch (FieldValidationException $e) {
- $this->assertTrue($entity->{$this->field_name}[0]['value'] != $term->tid, t('Term from wrong vocabulary does not cause validation error'));
+ $this->assertTrue($entity->{$this->field_name}[$langcode][0]['value'] != $term->tid, t('Term from wrong vocabulary does not cause validation error'));
}
$entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
$bad_term = $this->createTerm($this->createVocabulary());
- $entity->{$this->field_name}[0]['value'] = $bad_term->tid;
+ $entity->{$this->field_name}[$langcode][0]['value'] = $bad_term->tid;
try {
field_attach_validate('test_entity', $entity);
}
@@ -819,12 +822,13 @@ class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase {
$term = $this->createTerm($this->vocabulary);
// Display creation form.
+ $langcode = FIELD_LANGUAGE_NONE;
$this->drupalGet('test-entity/add/test-bundle');
- $this->assertFieldByName($this->field_name . '[value]', '', t('Widget is displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][value]", '', t('Widget is displayed'));
// Submit with some value.
$edit = array(
- $this->field_name . '[value]' => array($term->tid),
+ "{$this->field_name}[$langcode][value]" => array($term->tid),
);
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
diff --git a/modules/translation/translation.test b/modules/translation/translation.test
index 132ba9dd3..95f85dc9c 100644
--- a/modules/translation/translation.test
+++ b/modules/translation/translation.test
@@ -60,14 +60,15 @@ class TranslationTestCase extends DrupalWebTestCase {
// to return to the page then resubmitting the form without a refresh.
$edit = array();
$edit['title'] = $this->randomName();
- $edit['body[0][value]'] = $this->randomName();
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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[$langcode][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 +79,7 @@ class TranslationTestCase extends DrupalWebTestCase {
// Update translation and mark as updated.
$edit = array();
- $edit['body[0][value]'] = $this->randomName();
+ $edit["body[$langcode][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 +129,8 @@ class TranslationTestCase extends DrupalWebTestCase {
function createPage($title, $body, $language) {
$edit = array();
$edit['title'] = $title;
- $edit['body[0][value]'] = $body;
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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 +155,8 @@ class TranslationTestCase extends DrupalWebTestCase {
$edit = array();
$edit['title'] = $title;
- $edit['body[0][value]'] = $body;
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][0][value]"] = $body;
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Translation created.'));
diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test
index ce3c11ce3..ac6bf8b79 100644
--- a/modules/trigger/trigger.test
+++ b/modules/trigger/trigger.test
@@ -38,7 +38,8 @@ class TriggerContentTestCase extends DrupalWebTestCase {
$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);
+ $langcode = FIELD_LANGUAGE_NONE;
+ $edit["body[$langcode][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.
diff --git a/modules/user/user-profile.tpl.php b/modules/user/user-profile.tpl.php
index 8b50f7026..487c8bcf6 100644
--- a/modules/user/user-profile.tpl.php
+++ b/modules/user/user-profile.tpl.php
@@ -16,6 +16,13 @@
* included by modules. $user_profile['user_picture'] is available
* for showing the account picture.
*
+ * Field variables: for each field instance attached to the user a corresponding
+ * variable is defined, e.g. $user->field_example becomes $field_example. When
+ * needing to access a field's raw values, developers/themers are strongly
+ * encouraged to use these variables. Otherwise they will have to explicitly
+ * specify the desired field language, e.g. $user->field_example['en'], thus
+ * overriding any language negotiation rule that was previously applied.
+ *
* @see user-profile-category.tpl.php
* Where the html is handled for the group.
* @see user-profile-field.tpl.php