123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- <?php
- /**
- * @file
- * Internationalization (i18n) module - Field handling
- *
- * For string keys we use:
- * - field:[field_name]:[bundle]:property, when it is an instance property (linked to bundle)
- * - field:[field_name]:#property..., when it is a field property (that may have multiple values)
- */
- /**
- * Implements hook_menu().
- */
- function i18n_field_menu() {
- $items = array();
- // Ensure the following is not executed until field_bundles is working and
- // tables are updated. Needed to avoid errors on initial installation.
- if (!module_exists('field_ui') || defined('MAINTENANCE_MODE')) {
- return $items;
- }
- // Create tabs for all possible bundles. From field_ui_menu().
- foreach (entity_get_info() as $entity_type => $entity_info) {
- if ($entity_info['fieldable']) {
- foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
- if (isset($bundle_info['admin'])) {
- // Extract path information from the bundle.
- $path = $bundle_info['admin']['path'];
- // Different bundles can appear on the same path (e.g. %node_type and
- // %comment_node_type). To allow field_ui_menu_load() to extract the
- // actual bundle object from the translated menu router path
- // arguments, we need to identify the argument position of the bundle
- // name string ('bundle argument') and pass that position to the menu
- // loader. The position needs to be casted into a string; otherwise it
- // would be replaced with the bundle name string.
- if (isset($bundle_info['admin']['bundle argument'])) {
- $bundle_arg = $bundle_info['admin']['bundle argument'];
- $bundle_pos = (string) $bundle_arg;
- }
- else {
- $bundle_arg = $bundle_name;
- $bundle_pos = '0';
- }
- // This is the position of the %field_ui_menu placeholder in the
- // items below.
- $field_position = count(explode('/', $path)) + 1;
- // Extract access information, providing defaults.
- $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
- $access += array(
- 'access callback' => 'user_access',
- 'access arguments' => array('administer site configuration'),
- );
- $items["$path/fields/%field_ui_menu/translate"] = array(
- 'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
- 'title' => 'Translate',
- 'page callback' => 'i18n_field_page_translate',
- 'page arguments' => array($field_position),
- 'file' => 'i18n_field.pages.inc',
- 'type' => MENU_LOCAL_TASK,
- ) + $access;
- $items["$path/fields/%field_ui_menu/translate/%i18n_language"] = array(
- 'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
- 'title' => 'Instance',
- 'page callback' => 'i18n_field_page_translate',
- 'page arguments' => array($field_position, $field_position + 2),
- 'file' => 'i18n_field.pages.inc',
- 'type' => MENU_CALLBACK,
- ) + $access;
- }
- }
- }
- }
- return $items;
- }
- /**
- * Implements hook_hook_info().
- */
- function i18n_field_hook_info() {
- $hooks['i18n_field_info'] = array(
- 'group' => 'i18n',
- );
- return $hooks;
- }
- /**
- * Implements hook_field_attach_form().
- *
- * After the form fields are built. Translate title and description for fields with multiple values.
- */
- function i18n_field_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
- // Determine the list of instances to iterate on.
- list(, , $bundle) = entity_extract_ids($entity_type, $entity);
- $instances = field_info_instances($entity_type, $bundle);
- foreach ($instances as $field_name => $instance) {
- if (isset($form[$field_name])) {
- $langcode = $form[$field_name]['#language'];
- $field = &$form[$field_name];
- // Note: cardinality for unlimited fields is -1
- if (isset($field[$langcode]['#cardinality']) && $field[$langcode]['#cardinality'] != 1) {
- $translated = i18n_string_object_translate('field_instance', $instance);
- if (!empty($field[$langcode]['#title'])) {
- $field[$langcode]['#title'] = $translated['label'];
- }
- if (!empty($field[$langcode]['#description'])) {
- $field[$langcode]['#description'] = $translated['description'];
- }
- }
- }
- }
- }
- /**
- * Implements hook_field_formatter_info().
- */
- function i18n_field_field_formatter_info() {
- $types = array();
- foreach (i18n_field_type_info() as $type => $info) {
- if (!empty($info['translate_options'])) {
- $types[] = $type;
- }
- }
- return array(
- 'i18n_list_default' => array(
- 'label' => t('Default translated'),
- 'field types' => $types,
- ),
- );
- }
- /**
- * Implements hook_field_formatter_view().
- */
- function i18n_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
- $element = array();
- switch ($display['type']) {
- case 'i18n_list_default':
- if (($translate = i18n_field_type_info($field['type'], 'translate_options'))) {
- $allowed_values = $translate($field);
- }
- else {
- // Defaults to list_default behavior
- $allowed_values = list_allowed_values($field);
- }
- foreach ($items as $delta => $item) {
- if (isset($allowed_values[$item['value']])) {
- $output = field_filter_xss($allowed_values[$item['value']]);
- }
- else {
- // If no match was found in allowed values, fall back to the key.
- $output = field_filter_xss($item['value']);
- }
- $element[$delta] = array('#markup' => $output);
- }
- break;
- }
- return $element;
- }
- /**
- * Implements hook_field_widget_form_alter().
- *
- * Translate:
- * - Title (label)
- * - Description (help)
- * - Default value
- * - List options
- */
- function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
- global $language;
- // Don't translate if the widget is being shown on the field edit form.
- if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
- return;
- }
- // Skip if we are missing any of the parameters
- if (empty($context['field']) || empty($context['instance']) || empty($context['langcode'])) {
- return;
- }
- $field = $context['field'];
- $instance = $context['instance'];
- $langcode = $context['langcode'];
- // Get the element to alter. Account for inconsistencies in how the element
- // is built for different field types.
- if (isset($element[0]) && count($element) == 1) {
- // Single-value file fields and image fields.
- $alter_element = &$element[0];
- }
- elseif ($field['type'] == 'url' && $field['module'] == 'url' && $field['cardinality'] == 1) {
- $alter_element = &$element;
- }
- elseif (isset($element['value'])) {
- // Number fields. Single-value text fields.
- $alter_element = &$element['value'];
- }
- elseif ($field['type'] == 'entityreference' && isset($element['target_id'])) {
- // Entityreference fields using the entityreference_autocomplete widget.
- $alter_element = &$element['target_id'];
- }
- elseif ($field['type'] == 'node_reference' && isset($element['nid'])) {
- // The node_reference fields using the entityreference_autocomplete widget.
- $alter_element = &$element['nid'];
- }
- else {
- // All other fields.
- $alter_element = &$element;
- }
- // If a subelement has the same title as the parent, translate it instead.
- // Allows fields such as email and commerce_price to be translated.
- foreach (element_get_visible_children($element) as $key) {
- $single_value = ($field['cardinality'] == 1);
- $has_title = (isset($element['#title']) && isset($element[$key]['#title']));
- if ($single_value && $has_title && $element[$key]['#title'] == $element['#title']) {
- $alter_element = &$element[$key];
- break;
- }
- }
- // The field language may affect some variables (default) but not others (description will be in current page language)
- $i18n_langcode = empty($alter_element['#language']) || $alter_element['#language'] == LANGUAGE_NONE ? $language->language : $alter_element['#language'];
- // Translate instance to current page language and set to form_state
- // so it will be used for validation messages later.
- $instance_current = i18n_string_object_translate('field_instance', $instance);
- if (isset($form_state['field'][$instance['field_name']][$langcode]['instance'])) {
- $form_state['field'][$instance['field_name']][$langcode]['instance'] = $instance_current;
- }
- // Translate field title if set and it is the default one.
- if (!empty($instance_current['label']) && $instance_current['label'] != $instance['label']) {
- if (!empty($alter_element['#title']) && $alter_element['#title'] == check_plain($instance['label'])) {
- $alter_element['#title'] = check_plain($instance_current['label']);
- }
- }
- // Translate field description if set and it is the default one.
- if (!empty($instance_current['description']) && $instance_current['description'] != $instance['description']) {
- if (!empty($alter_element['#description'])) {
- // Allow single-value file fields and image fields to have their
- // descriptions translated. file_field_widget_form() passes the
- // description through theme('file_upload_help'), so i18n_field
- // must do the same.
- $filefield = in_array($field['type'], array('file', 'image'));
- $single_value = ($field['cardinality'] == 1);
- $no_default = empty($alter_element['#default_value']['fid']);
- if ($filefield && $single_value && $no_default) {
- $help_variables = array(
- 'description' => field_filter_xss($instance['description']),
- 'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
- );
- $original_description = theme('file_upload_help', $help_variables);
- if ($alter_element['#description'] == $original_description) {
- $help_variables = array(
- 'description' => field_filter_xss($instance_current['description']),
- 'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
- );
- $alter_element['#description'] = theme('file_upload_help', $help_variables);
- }
- }
- elseif ($alter_element['#description'] == field_filter_xss($instance['description'])) {
- $alter_element['#description'] = field_filter_xss($instance_current['description']);
- }
- }
- }
- // Translate list options.
- $has_options = (!empty($alter_element['#options']) || $field['type'] == 'list_boolean');
- $has_allowed_values = !empty($field['settings']['allowed_values']);
- $translate = i18n_field_type_info($field['type'], 'translate_options');
- if ($has_options && $has_allowed_values && $translate) {
- $alter_element['#options'] = $translate($field, $i18n_langcode);
- if (isset($alter_element['#properties']) && !empty($alter_element['#properties']['empty_option'])) {
- $label = theme('options_none', array('instance' => $instance, 'option' => $alter_element['#properties']['empty_option']));
- $alter_element['#options'] = array('_none' => $label) + $alter_element['#options'];
- }
- // Translate list_boolean fields using the checkboxes widget.
- if (!empty($alter_element['#title']) && $field['type'] == 'list_boolean' && !empty($alter_element['#on_value'])) {
- $on_value = $alter_element['#on_value'];
- $alter_element['#options'];
- $alter_element['#title'] = $alter_element['#options'][$on_value];
- // For using label instead of "On value".
- if ($instance['widget']['settings']['display_label']) {
- $alter_element['#title'] = $instance_current['label'];
- }
- }
- }
- // Check for more parameters, skip this part if missing.
- if (!isset($context['delta']) || !isset($context['items'])) {
- return;
- }
- $delta = $context['delta'];
- $items = $context['items'];
- // Translate default value.
- $has_default_value = (isset($alter_element['#default_value']) && !empty($instance['default_value'][$delta]['value']));
- $storage_has_value = !empty($items[$delta]['value']);
- $translate = i18n_field_type_info($field['type'], 'translate_default');
- if ($has_default_value && $storage_has_value && $translate) {
- // Compare the default value with the value currently in storage.
- if ($instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
- $alter_element['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
- }
- }
- }
- /**
- * Implements hook_field_attach_view_alter().
- */
- function i18n_field_field_attach_view_alter(&$output, $context) {
- foreach (element_children($output) as $field_name) {
- $element = &$output[$field_name];
- if (!empty($element['#entity_type']) && !empty($element['#field_name']) && !empty($element['#bundle'])) {
- $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
- // Translate field title if set
- if (!empty($instance['label'])) {
- $element['#title'] = i18n_field_translate_property($instance, 'label');
- }
- // Translate field description if set
- if (!empty($instance['description'])) {
- $element['#description'] = i18n_field_translate_property($instance, 'description');
- }
- }
- }
- }
- /**
- * Implements hook_field_create_field().
- */
- function i18n_field_field_create_field($field) {
- i18n_field_field_update_strings($field);
- }
- /**
- * Implements hook_field_create_instance().
- */
- function i18n_field_field_create_instance($instance) {
- i18n_field_instance_update_strings($instance);
- }
- /**
- * Implements hook_field_delete_instance().
- */
- function i18n_field_field_delete_instance($instance) {
- i18n_string_object_remove('field_instance', $instance);
- }
- /**
- * Implements hook_field_update_instance().
- */
- function i18n_field_field_update_instance($instance, $prior_instance) {
- i18n_field_instance_update_strings($instance);
- }
- /**
- * Implements hook_field_update_field().
- */
- function i18n_field_field_update_field($field) {
- i18n_field_field_update_strings($field);
- }
- /**
- * Update field strings
- */
- function i18n_field_field_update_strings($field) {
- i18n_string_object_update('field', $field);
- }
- /**
- * Update field instance strings
- */
- function i18n_field_instance_update_strings($instance) {
- i18n_string_object_update('field_instance', $instance);
- }
- /**
- * Returns the array of translated allowed values for a list field.
- *
- * The strings are not safe for output. Keys and values of the array should be
- * sanitized through field_filter_xss() before being displayed.
- *
- * @param $field
- * The field definition.
- *
- * @return
- * The array of allowed values. Keys of the array are the raw stored values
- * (number or text), values of the array are the display labels.
- */
- function i18n_field_translate_allowed_values($field, $langcode = NULL) {
- if (!empty($field['settings']['allowed_values'])) {
- return i18n_string_translate(array('field', $field['field_name'], '#allowed_values'), $field['settings']['allowed_values'], array('langcode' => $langcode, 'sanitize' => FALSE));
- }
- else {
- return array();
- }
- }
- /**
- * Translate field default.
- */
- function i18n_field_translate_default($instance, $value, $langcode = NULL) {
- // The default value does not need sanitizing in a text_textfield widget.
- $sanitize = !($instance['widget']['type'] == 'text_textfield' && $instance['widget']['module'] == 'text');
- return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode, 'sanitize' => $sanitize));
- }
- /**
- * Translate field property
- */
- function i18n_field_translate_property($instance, $property, $langcode = NULL) {
- // For performance reasons, we translate the whole instance once, which is cached.
- $instance = i18n_string_object_translate('field_instance', $instance, array('langcode' => $langcode));
- return $instance[$property];
- }
- /**
- * Get i18n information for translating fields.
- *
- * @param $type
- * Optional field type.
- * @param $property
- * Optional property to get from field type.
- *
- * @return
- * - The property for the field if $type and $property set.
- * - Array of properties for the field type if only $type is set.
- * - Array of translation information for all field types.
- */
- function i18n_field_type_info($type = NULL, $property = NULL) {
- $info = &drupal_static(__FUNCTION__);
- if (!isset($info)) {
- $info = module_invoke_all('i18n_field_info');
- drupal_alter('i18n_field_info', $info);
- }
- if ($property) {
- return isset($info[$type]) && isset($info[$type][$property]) ? $info[$type][$property] : NULL;
- }
- elseif ($type) {
- return isset($info[$type]) ? $info[$type] : array();
- }
- else {
- return $info;
- }
- }
- /**
- * Implements hook_field_info_alter().
- */
- function i18n_field_field_info_alter(&$field_info) {
- foreach(array_keys($field_info) as $type) {
- $field_info[$type]['property_callbacks'][] = 'i18n_field_entity_property_callback';
- }
- }
- /**
- * Prime the cache to avoid single db queries for entity fields / properties.
- *
- * This is mainly uses when large operations are occuring like a flush of the
- * entity_property_infos().
- */
- function i18n_field_prime_caches() {
- global $language;
- static $cache_primed;
- // Fill the cache. This should avoid single db queries when filling the
- // properties.
- if (empty($cache_primed)) {
- $cache_primed = TRUE;
- $text_group = i18n_string_textgroup('field');
- // Load all strings at once to avoid callbacks for each individual string.
- $text_group->load_strings();
- $text_group->multiple_translation_search(array('type' => '*', 'objectid' => '*', 'property' => '*'), $language->language);
- }
- }
- /**
- * Callback to translate entity property info for a fields.
- *
- * @see entity_metadata_field_entity_property_info()
- * @see entity_metadata_field_default_property_callback()
- * @see i18n_field_i18n_object_info_alter()
- * @see hook_module_implements_alter()
- */
- function i18n_field_entity_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
- global $language;
- // This could create a endless recursion if it's called during rebuilding the
- // cache for i18n_object_info(). So if the cache of i18n_object_info isn't
- // available yet we assume the worst case, leave the info alone but trigger a
- // rebuild of the property when hook_i18n_object_info_alter is invoked. At
- // that point the info is available and we can rely on it.
- if (!$info = &drupal_static('i18n_object_info')) {
- $i18n_field_entity_property_callback_fallback = &drupal_static(__FUNCTION__);
- $i18n_field_entity_property_callback_fallback = TRUE;
- return;
- }
- i18n_field_prime_caches();
- $name = $field['field_name'];
- $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
- $property['label'] = i18n_field_translate_property($instance, 'label', $language->language);
- }
- /**
- * Implements hook_i18n_object_info_alter().
- */
- function i18n_field_i18n_object_info_alter(&$info) {
- if (drupal_static('i18n_field_entity_property_callback')) {
- if ($info = drupal_static('i18n_object_info')) {
- // Clean static and permanent cache of the data and then re-run the property
- // building.
- // Use a lock to avoid stampeding.
- $lock_name = 'i18n_field_entity_property_callback_fallback:' . $GLOBALS['language']->language;
- // See if another request is already doing this. If so we bail out here as
- // we won't help with anything at the moment.
- if (!lock_may_be_available($lock_name)) {
- return;
- }
- if (lock_acquire($lock_name)) {
- i18n_field_prime_caches();
- // Inject translated properties.
- $entity_property_info = entity_get_property_info();
- foreach ($entity_property_info as $entity_type => $properties) {
- if (isset($properties['bundles'])) {
- foreach ($properties['bundles'] as $bundle => $bundle_properties) {
- if ($bundle_properties['properties']) {
- foreach ($bundle_properties['properties'] as $bundle_property => $bundle_property_info) {
- if ($instance = field_info_instance($entity_type, $bundle_property, $bundle)) {
- $property = &$entity_property_info[$entity_type]['bundles'][$instance['bundle']]['properties'][$bundle_property];
- $property['label'] = i18n_field_translate_property($instance, 'label', $GLOBALS['language']->language);
- }
- }
- }
- }
- }
- }
- // Inject into static cache.
- $entity_get_property_info = &drupal_static('entity_get_property_info', array());
- $entity_get_property_info = $entity_property_info;
- // Write permanent cache.
- cache_set('entity_property_info:' . $GLOBALS['language']->language, $entity_property_info);
- lock_release($lock_name);
- }
- }
- else {
- watchdog('i18n_field', 'Unable to run fall-back handling for entity property translation due missing "i18n_object_info" cache', array(), WATCHDOG_WARNING);
- }
- }
- }
- /**
- * Implements hook_module_implements_alter().
- */
- function i18n_field_module_implements_alter(&$implementations, $hook) {
- if ($hook == 'i18n_object_info_alter') {
- // Move our hook implementation to the bottom.
- $group = $implementations['i18n_field'];
- unset($implementations['i18n_field']);
- $implementations['i18n_field'] = $group;
- }
- }
|