summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2010-03-25 11:46:21 +0000
committerDries Buytaert <dries@buytaert.net>2010-03-25 11:46:21 +0000
commitcfe7f8e69f9156284e1daf57921d89de256abda2 (patch)
tree0fbf95426b255f46aa8df99c9db7bcbca3859b54
parent85969b0590391aae9b2995248d3dec5780b379da (diff)
downloadbrdo-cfe7f8e69f9156284e1daf57921d89de256abda2.tar.gz
brdo-cfe7f8e69f9156284e1daf57921d89de256abda2.tar.bz2
- Patch #657972 by plach, Berdir: make field fallback logic actually reusable.
-rw-r--r--modules/field/field.api.php34
-rw-r--r--modules/field/field.attach.inc34
-rw-r--r--modules/field/field.module33
-rw-r--r--modules/field/field.multilingual.inc199
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module4
-rw-r--r--modules/field/tests/field.test155
-rw-r--r--modules/field/tests/field_test.entity.inc13
-rw-r--r--modules/field/tests/field_test.module20
-rw-r--r--modules/field/tests/field_test.storage.inc4
-rw-r--r--modules/locale/locale.field.inc77
-rw-r--r--modules/locale/locale.info1
-rw-r--r--modules/locale/locale.install1
-rw-r--r--modules/locale/locale.module83
13 files changed, 419 insertions, 239 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 51489c28d..64b23e929 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1062,12 +1062,44 @@ function hook_field_attach_delete_revision($entity_type, $entity) {
* - obj_type: The type of $entity; e.g. 'node' or 'user'.
* - object: The entity with fields to render.
* - view_mode: View mode, e.g. 'full', 'teaser'...
- * - langcode: The language in which the field values will be displayed.
*/
function hook_field_attach_view_alter(&$output, $context) {
}
/**
+ * Act on field_language().
+ *
+ * This hook is invoked to alter the array of display languages for the given
+ * entity.
+ *
+ * @param $display_language
+ * A reference to an array of language codes keyed by field name.
+ * @param $context
+ * An associative array containing:
+ * - entity_type: The type of the entity to be displayed.
+ * - entity: The entity with fields to render.
+ * - langcode: The language code $entity has to be displayed in.
+ */
+function hook_field_language_alter(&$display_language, $context) {
+}
+
+/**
+ * Act on field_available_languages().
+ *
+ * This hook is invoked to alter the array of available languages for the given
+ * field.
+ *
+ * @param &$languages
+ * A reference to an array of language codes to be made available.
+ * @param $context
+ * An associative array containing:
+ * - entity_type: The type of the entity the field is attached to.
+ * - field: A field data structure.
+ */
+function hook_field_available_languages_alter(&$languages, $context) {
+}
+
+/**
* Act on field_attach_create_bundle.
*
* This hook is invoked after the field module has performed the operation.
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index 204c1fb25..cb22b164c 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -168,6 +168,9 @@ define('FIELD_STORAGE_INSERT', 'insert');
* - 'deleted': If TRUE, the function will operate on deleted fields
* as well as non-deleted fields. If unset or FALSE, only
* non-deleted fields are operated on.
+ * - 'language': A language code or an array of language codes keyed by field
+ * name. It will be used to narrow down to a single value the available
+ * languages to act on.
*/
function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
// Merge default options.
@@ -196,10 +199,14 @@ function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $opti
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']);
+
+ // Unless a language suggestion is provided we iterate on all the
+ // available languages.
+ $available_languages = field_available_languages($entity_type, $field);
+ $languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
// Initialize field translations according to the available languages.
- foreach (field_multilingual_available_languages($entity_type, $field, $suggested_languages) as $langcode) {
+ foreach ($languages as $langcode) {
$field_translations[$langcode] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
}
@@ -271,6 +278,10 @@ function _field_invoke($op, $entity_type, $entity, &$a = NULL, &$b = NULL, $opti
* - 'deleted': If TRUE, the function will operate on deleted fields
* as well as non-deleted fields. If unset or FALSE, only
* non-deleted fields are operated on.
+ * - 'language': A language code or an array of language codes keyed by field
+ * name. It will be used to narrow down to a single value the available
+ * languages to act on.
+ *
* @return
* An array of returned values keyed by entity id.
*/
@@ -321,8 +332,11 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
$grouped_entities[$field_id][$id] = $entities[$id];
// Extract the field values into a separate variable, easily accessed
// by hook implementations.
- $suggested_languages = empty($options['language']) ? NULL : array($options['language']);
- foreach (field_multilingual_available_languages($entity_type, $fields[$field_id], $suggested_languages) as $langcode) {
+ // Unless a language suggestion is provided we iterate on all the
+ // available languages.
+ $available_languages = field_available_languages($entity_type, $fields[$field_id]);
+ $languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
+ foreach ($languages as $langcode) {
$grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
}
}
@@ -492,7 +506,7 @@ function _field_invoke_multiple_default($op, $entity_type, $entities, &$a = NULL
*/
function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode = NULL) {
// If no language is provided use the default site language.
- $options = array('language' => field_multilingual_valid_language($langcode));
+ $options = array('language' => field_valid_language($langcode));
$form += (array) _field_invoke_default('form', $entity_type, $entity, $form, $form_state, $options);
// Add custom weight handling.
@@ -1200,9 +1214,12 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode = 'full')
* A renderable array for the field values.
*/
function field_attach_view($entity_type, $entity, $view_mode = 'full', $langcode = NULL) {
- // Invoke field_default_view(). If no language is provided, use the current
- // UI language.
- $options = array('language' => field_multilingual_valid_language($langcode, FALSE));
+ // Determine the actual language to display for each field, given the
+ // languages available in the field data.
+ $display_language = field_language($entity_type, $entity, NULL, $langcode);
+ $options = array('language' => $display_language);
+
+ // Invoke field_default_view().
$null = NULL;
$output = _field_invoke_default('view', $entity_type, $entity, $view_mode, $null, $options);
@@ -1219,7 +1236,6 @@ function field_attach_view($entity_type, $entity, $view_mode = 'full', $langcode
'obj_type' => $entity_type,
'object' => $entity,
'view_mode' => $view_mode,
- 'langcode' => $langcode,
);
drupal_alter('field_attach_view', $output, $context);
diff --git a/modules/field/field.module b/modules/field/field.module
index 2505c0fc2..eb3664916 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -496,10 +496,8 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display =
$output = array();
if ($field = field_info_field($field_name)) {
- $langcode = field_multilingual_valid_language($langcode, FALSE);
-
// Determine the langcode that will be used by language fallback.
- $langcode = current(field_multilingual_available_languages($entity_type, $field, array($langcode)));
+ $langcode = field_language($entity_type, $entity, $field_name, $langcode);
// Push the item as the single value for the field, and defer to
// field_view_field() to build the render array for the whole field.
@@ -588,7 +586,10 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
// Hook invocations are done through the _field_invoke() functions in
// 'single field' mode, to reuse the language fallback logic.
- $options = array('field_name' => $field_name, 'language' => field_multilingual_valid_language($langcode, FALSE));
+ // Determine the actual language to display for the field, given the
+ // languages available in the field data.
+ $display_language = field_language($entity_type, $entity, $field_name, $langcode);
+ $options = array('field_name' => $field_name, 'language' => $display_language);
$null = NULL;
// Invoke prepare_view steps if needed.
@@ -604,13 +605,12 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
// Build the renderable array.
$result = _field_invoke_default('view', $entity_type, $entity, $display, $null, $options);
- // Invoke hook_field_attach_view_alter() to tet other modules alter the
+ // Invoke hook_field_attach_view_alter() to let other modules alter the
// renderable array, as in a full field_attach_view() execution.
$context = array(
'obj_type' => $entity_type,
'object' => $entity,
'view_mode' => '_custom',
- 'langcode' => $langcode,
);
drupal_alter('field_attach_view', $result, $context);
@@ -624,6 +624,27 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
}
/**
+ * Returns the field items in the language they currently would be displayed.
+ *
+ * @param $entity_type
+ * The type of $entity.
+ * @param $entity
+ * The entity containing the data to be displayed.
+ * @param $field_name
+ * The field to be displayed.
+ * @param $langcode
+ * (optional) The language code $entity->{$field_name} has to be displayed in.
+ * Defaults to the current language.
+ *
+ * @return
+ * An array of field items keyed by delta if available, FALSE otherwise.
+ */
+function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) {
+ $langcode = field_language($entity_type, $entity, $field_name, $langcode);
+ return isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : FALSE;
+}
+
+/**
* Determine whether a field has any data.
*
* @param $field
diff --git a/modules/field/field.multilingual.inc b/modules/field/field.multilingual.inc
index 11e89b29b..16ace959b 100644
--- a/modules/field/field.multilingual.inc
+++ b/modules/field/field.multilingual.inc
@@ -14,74 +14,111 @@ function field_multilingual_settings_changed() {
}
/**
- * Collect the available languages for the given entity type and field.
+ * Collects 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 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.
+ * If the given field has language support enabled, an array of available
+ * languages will be returned, otherwise only 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 $entity_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($entity_type, $field, $suggested_languages = NULL) {
+function field_available_languages($entity_type, $field) {
$field_languages = &drupal_static(__FUNCTION__, array());
$field_name = $field['field_name'];
- if (!isset($field_languages[$field_name]) || !empty($suggested_languages)) {
- $translation_handlers = field_multilingual_check_translation_handlers($entity_type);
-
- if ($translation_handlers && $field['translatable']) {
- // The returned languages are a subset of the intersection of enabled ones
- // and suggested ones.
- $available_languages = field_multilingual_content_languages();
- $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($entity_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;
- }
+ if (!isset($field_languages[$entity_type][$field_name])) {
+ // If the field has language support enabled we retrieve an (alterable) list
+ // of enabled languages, otherwise we return just LANGUAGE_NONE.
+ if (field_is_translatable($entity_type, $field)) {
+ $languages = field_content_languages();
+ // Let other modules alter the available languages.
+ $context = array('entity_type' => $entity_type, 'field' => $field);
+ drupal_alter('field_available_languages', $languages, $context);
+ $field_languages[$entity_type][$field_name] = $languages;
}
else {
- $result = $field_languages[$field_name] = array(LANGUAGE_NONE);
+ $field_languages[$entity_type][$field_name] = array(LANGUAGE_NONE);
}
}
- else {
- $result = $field_languages[$field_name];
+
+ return $field_languages[$entity_type][$field_name];
+}
+
+/**
+ * Process the given language suggestion based on the available languages.
+ *
+ * If a non-empty language suggestion is provided it must appear among the
+ * available languages, otherwise it will be ignored.
+ *
+ * @param $available_languages
+ * An array of valid language codes.
+ * @param $language_suggestion
+ * A language code or an array of language codes keyed by field name.
+ * @param $field_name
+ * The name of the field being processed.
+ *
+ * @return
+ * An array of valid language codes.
+ */
+function _field_language_suggestion($available_languages, $language_suggestion, $field_name) {
+ // Handle possible language suggestions.
+ if (!empty($language_suggestion)) {
+ // We might have an array of language suggestions keyed by field name.
+ if (is_array($language_suggestion) && isset($language_suggestion[$field_name])) {
+ $language_suggestion = $language_suggestion[$field_name];
+ }
+
+ // If we have a language suggestion and the suggested language is available,
+ // we return only it.
+ if (in_array($language_suggestion, $available_languages)) {
+ $available_languages = array($language_suggestion);
+ }
}
- return $result;
+ return $available_languages;
}
/**
- * Return available content languages.
+ * Returns available content languages.
*
* The languages that may be associated to fields include LANGUAGE_NONE.
*
* @return
* An array of language codes.
*/
-function field_multilingual_content_languages() {
- return array_keys(language_list() + array(LANGUAGE_NONE => NULL));
+function field_content_languages() {
+ $languages = language_list('enabled');
+ return array_keys($languages[1] + array(LANGUAGE_NONE => NULL));
}
/**
- * Check if a module is registered as a translation handler for a given entity.
+ * Checks whether a field has language support.
+ *
+ * A field has language support enabled if its 'translatable' property is set to
+ * TRUE, and its entity type has at least one translation handler registered.
+ *
+ * @param $entity_type
+ * The type of the entity the field is attached to.
+ * @param $field
+ * A field data structure.
+ *
+ * @return
+ * TRUE if the field can be translated.
+ */
+function field_is_translatable($entity_type, $field) {
+ return $field['translatable'] && field_has_translation_handler($entity_type);
+}
+
+/**
+ * Checks if a module is registered as a translation handler for a given entity.
*
* If no handler is passed, simply check if there is any translation handler
* enabled for the given entity type.
@@ -89,16 +126,18 @@ function field_multilingual_content_languages() {
* @param $entity_type
* The type of the entity whose fields are to be translated.
* @param $handler
- * The name of the handler to be checked.
+ * (optional) The name of the handler to be checked. Defaults to NULL.
*
* @return
- * TRUE, if the handler is allowed to manage field translations.
+ * TRUE, if the given handler is allowed to manage field translations. If no
+ * handler is passed, TRUE means there is at least one registered translation
+ * handler.
*/
-function field_multilingual_check_translation_handlers($entity_type, $handler = NULL) {
+function field_has_translation_handler($entity_type, $handler = NULL) {
$entity_info = entity_get_info($entity_type);
if (isset($handler)) {
- return isset($entity_info['translation'][$handler]) && !empty($entity_info['translation'][$handler]);
+ return !empty($entity_info['translation'][$handler]);
}
elseif (isset($entity_info['translation'])) {
foreach ($entity_info['translation'] as $handler_info) {
@@ -113,7 +152,7 @@ function field_multilingual_check_translation_handlers($entity_type, $handler =
}
/**
- * Helper function to ensure that a given language code is valid.
+ * Ensures 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
@@ -127,16 +166,76 @@ function field_multilingual_check_translation_handlers($entity_type, $handler =
* @return
* A valid language code.
*/
-function field_multilingual_valid_language($langcode, $default = TRUE) {
- $enabled_languages = field_multilingual_content_languages();
+function field_valid_language($langcode, $default = TRUE) {
+ $enabled_languages = field_content_languages();
if (in_array($langcode, $enabled_languages)) {
return $langcode;
}
global $language_content;
- $langcode = $default ? language_default('language') : $language_content->language;
- if (in_array($langcode, $enabled_languages)) {
- return $langcode;
+ return $default ? language_default('language') : $language_content->language;
+}
+
+/**
+ * Returns the display language for the fields attached to the given entity.
+ *
+ * The actual language for each given field is determined based on the requested
+ * language and the actual data available in the fields themselves.
+ * If there is no registered translation handler for the given entity type, the
+ * display language to be used is just LANGUAGE_NONE, as no other language code
+ * is allowed by field_available_languages().
+ * If translation handlers are found, we let modules provide alternative display
+ * languages for fields not having the requested language available.
+ * Core language fallback rules are provided by locale_field_language_fallback()
+ * which is called by locale_field_language_alter().
+ *
+ * @param $entity_type
+ * The type of $entity.
+ * @param $entity
+ * The entity to be displayed.
+ * @param $field_name
+ * (optional) The name of the field to be displayed. Defaults to NULL. If
+ * no value is specified, the display languages for every field attached to
+ * the given entity will be returned.
+ * @param $langcode
+ * (optional) The language code $entity has to be displayed in. Defaults to
+ * NULL. If no value is given the current language will be used.
+ *
+ * @return
+ * A language code if a field name is specified, an array of language codes
+ * keyed by field name otherwise.
+ */
+function field_language($entity_type, $entity, $field_name = NULL, $langcode = NULL) {
+ $display_languages = &drupal_static(__FUNCTION__, array());
+ list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
+ $langcode = field_valid_language($langcode, FALSE);
+
+ if (!isset($display_languages[$entity_type][$id][$langcode])) {
+ $display_language = array();
+
+ // By default display language is set to LANGUAGE_NONE. It is up to
+ // translation handlers to implement language fallback rules.
+ foreach (field_info_instances($entity_type, $bundle) as $instance) {
+ $display_language[$instance['field_name']] = LANGUAGE_NONE;
+ }
+
+ if (field_has_translation_handler($entity_type)) {
+ $context = array(
+ 'entity_type' => $entity_type,
+ 'entity' => $entity,
+ 'language' => $langcode,
+ );
+ drupal_alter('field_language', $display_language, $context);
+ }
+
+ $display_languages[$entity_type][$id][$langcode] = $display_language;
+ }
+
+ $display_language = $display_languages[$entity_type][$id][$langcode];
+
+ // Single-field mode.
+ if (isset($field_name)) {
+ return isset($display_language[$field_name]) ? $display_language[$field_name] : FALSE;
}
- // @todo Throw a more specific exception.
- throw new FieldException('No valid content language could be found.');
+
+ return $display_language;
}
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 8229c523f..2e7512627 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -311,7 +311,7 @@ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fi
->fields('t')
->condition('etid', $etid)
->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
- ->condition('language', field_multilingual_available_languages($entity_type, $field), 'IN')
+ ->condition('language', field_available_languages($entity_type, $field), 'IN')
->orderBy('delta');
if (empty($options['deleted'])) {
@@ -356,7 +356,7 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
- $all_languages = field_multilingual_available_languages($entity_type, $field);
+ $all_languages = field_available_languages($entity_type, $field);
$field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name));
// Delete and insert, rather than update, in case a value was added.
diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test
index f79ebd0b6..3b5cdc7b5 100644
--- a/modules/field/tests/field.test
+++ b/modules/field/tests/field.test
@@ -2604,8 +2604,7 @@ class FieldInstanceCrudTestCase extends FieldTestCase {
* 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().
+ * that only the correct values are returned by field_available_languages().
*/
class FieldTranslationsTestCase extends FieldTestCase {
public static function getInfo() {
@@ -2628,9 +2627,6 @@ class FieldTranslationsTestCase extends FieldTestCase {
'type' => 'test_field',
'cardinality' => 4,
'translatable' => TRUE,
- 'settings' => array(
- 'test_hook_in' => FALSE,
- ),
);
field_create_field($field);
$this->field = field_read_field($this->field_name);
@@ -2639,19 +2635,6 @@ class FieldTranslationsTestCase extends FieldTestCase {
'field_name' => $this->field_name,
'object_type' => $this->obj_type,
'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($instance);
$this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle');
@@ -2663,64 +2646,34 @@ class FieldTranslationsTestCase extends FieldTestCase {
}
/**
- * Ensure that only valid values are returned by field_multilingual_available_languages().
+ * Ensures that only valid values are returned by field_available_languages().
*/
function testFieldAvailableLanguages() {
// Test 'translatable' fieldable info.
field_test_entity_info_translatable('test_entity', FALSE);
$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, $suggested_languages);
- $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('Untranslatable entity: suggested language ignored.'));
// Enable field translations for the entity.
field_test_entity_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(LANGUAGE_NONE, $available_languages), t('%language is an available language.', array('%language' => LANGUAGE_NONE)));
+ variable_set('field_test_field_available_languages_alter', TRUE);
+ $enabled_languages = field_content_languages();
+ $available_languages = field_available_languages($this->obj_type, $this->field);
foreach ($available_languages as $delta => $langcode) {
- if ($langcode != LANGUAGE_NONE) {
+ if ($langcode != 'xx' && $langcode != 'en') {
$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.'));
+ $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx')));
+ $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en')));
- // Test field_multilingual_available_languages() behavior for untranslatable fields.
+ // Test field_available_languages() behavior for untranslatable fields.
$this->field['translatable'] = FALSE;
$this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
- $available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
- $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === 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.'));
+ $available_languages = field_available_languages($this->obj_type, $this->field);
+ $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.'));
}
/**
@@ -2731,10 +2684,10 @@ class FieldTranslationsTestCase extends FieldTestCase {
$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().
+ // the result of field_available_languages().
$values = array();
$extra_languages = mt_rand(1, 4);
- $languages = $available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
+ $languages = $available_languages = field_available_languages($this->obj_type, $this->field);
for ($i = 0; $i < $extra_languages; ++$i) {
$languages[] = $this->randomString(2);
}
@@ -2765,14 +2718,14 @@ class FieldTranslationsTestCase extends FieldTestCase {
$entities = array();
$entity_type = 'test_entity';
$entity_count = mt_rand(1, 5);
- $available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
+ $available_languages = field_available_languages($this->obj_type, $this->field);
for ($id = 1; $id <= $entity_count; ++$id) {
$entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']);
$languages = $available_languages;
// Populate some extra languages to check whether _field_invoke()
- // correctly uses the result of field_multilingual_available_languages().
+ // correctly uses the result of field_available_languages().
$extra_languages = mt_rand(1, 4);
for ($i = 0; $i < $extra_languages; ++$i) {
$languages[] = $this->randomString(2);
@@ -2815,7 +2768,7 @@ class FieldTranslationsTestCase extends FieldTestCase {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
$field_translations = array();
- $available_languages = field_multilingual_available_languages($entity_type, $this->field);
+ $available_languages = field_available_languages($entity_type, $this->field);
$this->assertTrue(count($available_languages) > 1, t('Field is translatable.'));
foreach ($available_languages as $langcode) {
$field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
@@ -2836,6 +2789,82 @@ class FieldTranslationsTestCase extends FieldTestCase {
$this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode)));
}
}
+
+ /**
+ * Tests display language logic for translatable fields.
+ */
+ function testFieldDisplayLanguage() {
+ $field_name = drupal_strtolower($this->randomName() . '_field_name');
+ $entity_type = 'test_entity';
+
+ // We need an additional field here to properly test display language
+ // suggestions.
+ $field = array(
+ 'field_name' => $field_name,
+ 'type' => 'test_field',
+ 'cardinality' => 2,
+ 'translatable' => TRUE,
+ );
+ field_create_field($field);
+
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'object_type' => $entity_type,
+ 'bundle' => 'test_bundle',
+ );
+ field_create_instance($instance);
+
+ $entity = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ $instances = field_info_instances($entity_type, $bundle);
+
+ $enabled_languages = field_content_languages();
+ $languages = array();
+
+ // Generate field translations for languages different from the first
+ // enabled.
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ do {
+ // Index 0 is reserved for the requested language, this way we ensure
+ // that no field is actually populated with it.
+ $langcode = $enabled_languages[mt_rand(1, count($enabled_languages) - 1)];
+ }
+ while (isset($languages[$langcode]));
+ $languages[$langcode] = TRUE;
+ $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues($field['cardinality']);
+ }
+
+ // Test multiple-fields display languages for untranslatable entities.
+ field_test_entity_info_translatable($entity_type, FALSE);
+ drupal_static_reset('field_language');
+ $requested_language = $enabled_languages[0];
+ $display_language = field_language($entity_type, $entity, NULL, $requested_language);
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE)));
+ }
+
+ // Test multiple-fields display languages for translatable entities.
+ field_test_entity_info_translatable($entity_type, TRUE);
+ drupal_static_reset('field_language');
+ $display_language = field_language($entity_type, $entity, NULL, $requested_language);
+
+ foreach ($instances as $instance) {
+ $field_name = $instance['field_name'];
+ $langcode = $display_language[$field_name];
+ // As the requested language was not assinged to any field, if the
+ // returned language is defined for the current field, core fallback rules
+ // were successfully applied.
+ $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
+ }
+
+ // Test single-field display language.
+ drupal_static_reset('field_language');
+ $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language);
+ $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode)));
+ }
}
/**
diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc
index 3196ebd19..eef3dbf73 100644
--- a/modules/field/tests/field_test.entity.inc
+++ b/modules/field/tests/field_test.entity.inc
@@ -64,6 +64,19 @@ function field_test_entity_info_alter(&$entity_info) {
}
/**
+ * Helper function to enable entity translations.
+ */
+function field_test_entity_info_translatable($entity_type = NULL, $translatable = NULL) {
+ drupal_static_reset('field_has_translation_handler');
+ $stored_value = &drupal_static(__FUNCTION__, array());
+ if (isset($entity_type)) {
+ $stored_value[$entity_type] = $translatable;
+ entity_info_cache_clear();
+ }
+ return $stored_value;
+}
+
+/**
* Creates a new bundle for test_entity entities.
*
* @param $bundle
diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module
index 7d0b8366d..51cd0df70 100644
--- a/modules/field/tests/field_test.module
+++ b/modules/field/tests/field_test.module
@@ -87,27 +87,23 @@ function field_test_field_test_op_multiple($entity_type, $entities, $field, $ins
}
/**
- * Implements hook_field_languages().
+ * Implements hook_field_available_languages_alter().
*/
-function field_test_field_languages($entity_type, $field, &$languages) {
- if ($field['settings']['test_hook_in']) {
+function field_test_field_available_languages_alter(&$languages, $context) {
+ if (variable_get('field_test_field_available_languages_alter', FALSE)) {
// Add an unavailable language.
$languages[] = 'xx';
// Remove an available language.
- unset($languages[0]);
+ $index = array_search('en', $languages);
+ unset($languages[$index]);
}
}
/**
- * Helper function to enable entity translations.
+ * Implements hook_field_language_alter().
*/
-function field_test_entity_info_translatable($entity_type = NULL, $translatable = NULL) {
- $stored_value = &drupal_static(__FUNCTION__, array());
- if (isset($entity_type)) {
- $stored_value[$entity_type] = $translatable;
- entity_info_cache_clear();
- }
- return $stored_value;
+function field_test_field_language_alter(&$display_language, $context) {
+ locale_field_language_fallback($display_language, $context['entity'], $context['language']);
}
/**
diff --git a/modules/field/tests/field_test.storage.inc b/modules/field/tests/field_test.storage.inc
index 617e3b6ae..6ce272040 100644
--- a/modules/field/tests/field_test.storage.inc
+++ b/modules/field/tests/field_test.storage.inc
@@ -95,7 +95,7 @@ function field_test_field_storage_load($entity_type, $entities, $age, $fields, $
foreach ($field_data[$sub_table] as $row) {
if ($row->type == $entity_type && (!$row->deleted || $options['deleted'])) {
if (($load_current && in_array($row->entity_id, $ids)) || (!$load_current && in_array($row->revision_id, $ids))) {
- if (in_array($row->language, field_multilingual_available_languages($entity_type, $field))) {
+ if (in_array($row->language, field_available_languages($entity_type, $field))) {
if (!isset($delta_count[$row->entity_id][$row->language])) {
$delta_count[$row->entity_id][$row->language] = 0;
}
@@ -127,7 +127,7 @@ function field_test_field_storage_write($entity_type, $entity, $op, $fields) {
$field_name = $field['field_name'];
$field_data = &$data[$field_id];
- $all_languages = field_multilingual_available_languages($entity_type, $field);
+ $all_languages = field_available_languages($entity_type, $field);
$field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name));
// Delete and insert, rather than update, in case a value was added.
diff --git a/modules/locale/locale.field.inc b/modules/locale/locale.field.inc
index 45a94bcfe..e69de29bb 100644
--- a/modules/locale/locale.field.inc
+++ b/modules/locale/locale.field.inc
@@ -1,77 +0,0 @@
-<?php
-// $Id$
-
-/**
- * @file
- * Field API multilingual handling.
- */
-
-/**
- * Form submit handler for node_form().
- *
- * Update the field language according to the node language, changing the
- * previous language if necessary.
- */
-function locale_field_node_form_update_field_language($form, &$form_state, $reset_previous = TRUE) {
- $node = (object) $form_state['values'];
- $available_languages = field_multilingual_content_languages();
- // @todo: Unify language neutral language codes.
- $selected_language = empty($node->language) ? LANGUAGE_NONE : $node->language;
- list(, , $bundle) = entity_extract_ids('node', $node);
-
- foreach (field_info_instances('node', $bundle) as $instance) {
- $field_name = $instance['field_name'];
- $field = field_info_field($field_name);
- $previous_language = $form[$field_name]['#language'];
-
- // Handle a possible language change: previous language values are deleted,
- // new ones are inserted.
- if ($field['translatable'] && $previous_language != $selected_language) {
- $form_state['values'][$field_name][$selected_language] = $node->{$field_name}[$previous_language];
- if ($reset_previous) {
- $form_state['values'][$field_name][$previous_language] = array();
- }
- }
- }
-}
-
-/**
- * Apply fallback rules to the given object.
- *
- * Parameters are the same of hook_field_attach_view().
- */
-function locale_field_fallback_view(&$output, $context) {
- // Lazily init fallback values and candidates to avoid unnecessary calls.
- $fallback_values = array();
- $fallback_candidates = NULL;
- list(, , $bundle) = entity_extract_ids($context['obj_type'], $context['object']);
-
- foreach (field_info_instances($context['obj_type'], $bundle) as $instance) {
- $field_name = $instance['field_name'];
- $field = field_info_field($field_name);
-
- // If the items array is empty then we have a missing field translation.
- // @todo: Verify this assumption.
- if (isset($output[$field_name]) && count(element_children($output[$field_name])) == 0) {
- if (!isset($fallback_candidates)) {
- require_once DRUPAL_ROOT . '/includes/language.inc';
- $fallback_candidates = language_fallback_get_candidates();
- }
-
- foreach ($fallback_candidates as $langcode) {
- // Again if we have a non-empty array we assume the field translation is
- // valid.
- if (!empty($context['object']->{$field_name}[$langcode])) {
- // Cache fallback values per language as fields might have different
- // fallback values.
- if (!isset($fallback_values[$langcode])) {
- $fallback_values[$langcode] = field_attach_view($context['obj_type'], $context['object'], $context['view_mode'], $langcode);
- }
- // We are done, skip to the next field.
- $output[$field_name] = $fallback_values[$langcode][$field_name];
- break;
- }
- }
- }
- }
-}
diff --git a/modules/locale/locale.info b/modules/locale/locale.info
index 181f0a191..461f207e9 100644
--- a/modules/locale/locale.info
+++ b/modules/locale/locale.info
@@ -6,7 +6,6 @@ version = VERSION
core = 7.x
files[] = locale.module
files[] = locale.install
-files[] = locale.field.inc
files[] = locale.admin.inc
files[] = locale.test
configure = admin/config/regional/language
diff --git a/modules/locale/locale.install b/modules/locale/locale.install
index 50f9605d5..13c2ee93f 100644
--- a/modules/locale/locale.install
+++ b/modules/locale/locale.install
@@ -113,6 +113,7 @@ function locale_uninstall() {
variable_del('locale_cache_strings');
variable_del('locale_js_directory');
variable_del('javascript_parsed');
+ variable_del('locale_field_language_fallback');
foreach (language_types() as $type) {
variable_del("language_negotiation_$type");
diff --git a/modules/locale/locale.module b/modules/locale/locale.module
index 667f7cd3a..48a6bfa1c 100644
--- a/modules/locale/locale.module
+++ b/modules/locale/locale.module
@@ -410,13 +410,27 @@ function locale_form_alter(&$form, &$form_state, $form_id) {
/**
* Form submit handler for node_form().
*
- * Check if Locale is registered as a translation handler and handle possible
+ * Checks if Locale is registered as a translation handler and handle possible
* node language changes.
*/
function locale_field_node_form_submit($form, &$form_state) {
- if (field_multilingual_check_translation_handlers('node', 'locale')) {
- module_load_include('inc', 'locale', 'locale.field');
- locale_field_node_form_update_field_language($form, $form_state);
+ if (field_has_translation_handler('node', 'locale')) {
+ $node = (object) $form_state['values'];
+ $available_languages = field_content_languages();
+ list(, , $bundle) = entity_extract_ids('node', $node);
+
+ foreach (field_info_instances('node', $bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ $previous_language = $form[$field_name]['#language'];
+
+ // Handle a possible language change: new language values are inserted,
+ // previous ones are deleted.
+ if ($field['translatable'] && $previous_language != $node->language) {
+ $form_state['values'][$field_name][$node->language] = $node->{$field_name}[$previous_language];
+ $form_state['values'][$field_name][$previous_language] = array();
+ }
+ }
}
}
@@ -438,20 +452,57 @@ function locale_theme() {
}
/**
- * Implements hook_field_attach_view_alter().
+ * Implements hook_field_language_alter().
*/
-function locale_field_attach_view_alter(&$output, $context) {
- // In locale_field_fallback_view() we might call field_attach_view(). The
- // static variable avoids unnecessary recursion.
- static $recursion;
+function locale_field_language_alter(&$display_language, $context) {
+ // Do not apply core language fallback rules if they are disabled or if Locale
+ // is not registered as a translation handler.
+ if (variable_get('locale_field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'locale')) {
+ locale_field_language_fallback($display_language, $context['entity'], $context['language']);
+ }
+}
- // Do not apply fallback rules if disabled or if Locale is not registered as a
- // translation handler.
- if (!$recursion && variable_get('locale_field_fallback_view', TRUE) && field_multilingual_check_translation_handlers($context['obj_type'], 'locale')) {
- $recursion = TRUE;
- module_load_include('inc', 'locale', 'locale.field');
- locale_field_fallback_view($output, $context);
- $recursion = FALSE;
+/**
+ * Applies language fallback rules to the fields attached to the given entity.
+ *
+ * Core language fallback rules simply check if fields have a field translation
+ * for the requested language code. If so the requested language is returned,
+ * otherwise all the fallback candidates are inspected to see if there is a
+ * field translation available in another language.
+ * By default this is called by locale_field_language_alter(), but this
+ * behavior can be disabled by setting the 'locale_field_language_fallback'
+ * variable to FALSE.
+ *
+ * @param $display_language
+ * A reference to an array of language codes keyed by field name.
+ * @param $entity
+ * The entity to be displayed.
+ * @param $langcode
+ * The language code $entity has to be displayed in.
+ */
+function locale_field_language_fallback(&$display_language, $entity, $langcode) {
+ // Lazily init fallback candidates to avoid unnecessary calls.
+ $fallback_candidates = NULL;
+ $field_languages = array();
+
+ foreach ($display_language as $field_name => $field_language) {
+ // If the requested language is defined for the current field use it,
+ // otherwise search for a fallback value among the fallback candidates.
+ if (isset($entity->{$field_name}[$langcode])) {
+ $display_language[$field_name] = $langcode;
+ }
+ elseif (!empty($entity->{$field_name})) {
+ if (!isset($fallback_candidates)) {
+ require_once DRUPAL_ROOT . '/includes/language.inc';
+ $fallback_candidates = language_fallback_get_candidates();
+ }
+ foreach ($fallback_candidates as $fallback_language) {
+ if (isset($entity->{$field_name}[$fallback_language])) {
+ $display_language[$field_name] = $fallback_language;
+ break;
+ }
+ }
+ }
}
}