diff options
author | Dries Buytaert <dries@buytaert.net> | 2010-03-25 11:46:21 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2010-03-25 11:46:21 +0000 |
commit | cfe7f8e69f9156284e1daf57921d89de256abda2 (patch) | |
tree | 0fbf95426b255f46aa8df99c9db7bcbca3859b54 | |
parent | 85969b0590391aae9b2995248d3dec5780b379da (diff) | |
download | brdo-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.php | 34 | ||||
-rw-r--r-- | modules/field/field.attach.inc | 34 | ||||
-rw-r--r-- | modules/field/field.module | 33 | ||||
-rw-r--r-- | modules/field/field.multilingual.inc | 199 | ||||
-rw-r--r-- | modules/field/modules/field_sql_storage/field_sql_storage.module | 4 | ||||
-rw-r--r-- | modules/field/tests/field.test | 155 | ||||
-rw-r--r-- | modules/field/tests/field_test.entity.inc | 13 | ||||
-rw-r--r-- | modules/field/tests/field_test.module | 20 | ||||
-rw-r--r-- | modules/field/tests/field_test.storage.inc | 4 | ||||
-rw-r--r-- | modules/locale/locale.field.inc | 77 | ||||
-rw-r--r-- | modules/locale/locale.info | 1 | ||||
-rw-r--r-- | modules/locale/locale.install | 1 | ||||
-rw-r--r-- | modules/locale/locale.module | 83 |
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; + } + } + } } } |