field.multilingual.inc 11.4 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5 6 7 8
 * Functions implementing Field API multilingual support.
 */

/**
9
 * @defgroup field_language Field Language API
10 11 12 13 14 15 16 17 18 19 20 21 22
 * @{
 * Handling of multilingual fields.
 *
 * Fields natively implement multilingual support, and all fields use the
 * following structure:
 * @code
 * $entity->{$field_name}[$langcode][$delta][$column_name]
 * @endcode
 * Every field can hold a single or multiple value for each language belonging
 * to the available languages set:
 * - For untranslatable fields this set only contains LANGUAGE_NONE.
 * - For translatable fields this set can contain any language code. By default
 *   it is the list returned by field_content_languages(), which contains all
23
 *   installed languages with the addition of LANGUAGE_NONE. This default can be
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
 *   altered by modules implementing hook_field_available_languages_alter().
 *
 * The available languages for a particular field are returned by
 * field_available_languages(). Whether a field is translatable is determined by
 * calling field_is_translatable(), which checks the $field['translatable']
 * property returned by field_info_field(), and whether there is at least one
 * translation handler available for the field. A translation handler is a
 * module registering itself via hook_entity_info() to handle field
 * translations.
 *
 * By default, _field_invoke() and _field_invoke_multiple() are processing a
 * field in all available languages, unless they are given a language
 * suggestion. Based on that suggestion, _field_language_suggestion() determines
 * the languages to act on.
 *
 * Most field_attach_*() functions act on all available languages, except for
 * the following:
 * - field_attach_form() only takes a single language code, specifying which
 *   language the field values will be submitted in.
 * - field_attach_view() requires the language the entity will be displayed in.
 *   Since it is unknown whether a field translation exists for the requested
 *   language, the translation handler is responsible for performing one of the
 *   following actions:
 *   - Ignore missing translations, i.e. do not show any field values for the
 *     requested language. For example, see locale_field_language_alter().
 *   - Provide a value in a different language as fallback. By default, the
 *     fallback logic is applied separately to each field to ensure that there
 *     is a value for each field to display.
 *   The field language fallback logic relies on the global language fallback
 *   configuration. Therefore, the displayed field values can be in the
 *   requested language, but may be different if no values for the requested
 *   language are available. The default language fallback rules inspect all the
 *   enabled languages ordered by their weight. This behavior can be altered or
 *   even disabled by modules implementing hook_field_language_alter(), making
 *   it possible to choose the first approach. The display language for each
 *   field is returned by field_language().
60
 *
61 62
 * See @link field Field API @endlink for information about the other parts of
 * the Field API.
63 64
 */

65 66 67 68 69 70 71 72 73 74 75 76 77 78
/**
 * Implements hook_locale_language_insert().
 */
function field_locale_language_insert() {
  field_info_cache_clear();
}

/**
 * Implements hook_locale_language_update().
 */
function field_locale_language_update() {
  field_info_cache_clear();
}

79
/**
80
 * Implements hook_locale_language_delete().
81
 */
82
function field_locale_language_delete() {
83
  field_info_cache_clear();
84 85
}

86
/**
87
 * Collects the available languages for the given entity type and field.
88
 *
89 90 91 92 93
 * 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.
94
 *
95
 * @param $entity_type
96 97 98
 *   The type of the entity the field is attached to, e.g. 'node' or 'user'.
 * @param $field
 *   A field structure.
99
 *
100 101 102
 * @return
 *   An array of valid language codes.
 */
103
function field_available_languages($entity_type, $field) {
104 105 106 107 108
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['field_languages'] = &drupal_static(__FUNCTION__);
  }
  $field_languages = &$drupal_static_fast['field_languages'];
109 110
  $field_name = $field['field_name'];

111 112 113 114 115 116 117 118 119
  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;
120 121
    }
    else {
122
      $field_languages[$entity_type][$field_name] = array(LANGUAGE_NONE);
123 124
    }
  }
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

  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);
    }
158 159
  }

160
  return $available_languages;
161 162 163
}

/**
164
 * Returns available content languages.
165
 *
166
 * The languages that may be associated to fields include LANGUAGE_NONE.
167 168 169 170
 *
 * @return
 *   An array of language codes.
 */
171
function field_content_languages() {
172
  return array_keys(language_list() + array(LANGUAGE_NONE => NULL));
173 174 175
}

/**
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
 * 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.
195
 *
196 197 198
 * If no handler is passed, simply check if there is any translation handler
 * enabled for the given entity type.
 *
199
 * @param $entity_type
200 201
 *   The type of the entity whose fields are to be translated.
 * @param $handler
202
 *   (optional) The name of the handler to be checked. Defaults to NULL.
203
 *
204
 * @return
205 206 207
 *   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.
208
 */
209
function field_has_translation_handler($entity_type, $handler = NULL) {
210
  $entity_info = entity_get_info($entity_type);
211 212

  if (isset($handler)) {
213
    return !empty($entity_info['translation'][$handler]);
214
  }
215 216
  elseif (isset($entity_info['translation'])) {
    foreach ($entity_info['translation'] as $handler_info) {
217 218 219 220 221 222 223 224
      // The translation handler must use a non-empty data structure.
      if (!empty($handler_info)) {
        return TRUE;
      }
    }
  }

  return FALSE;
225 226 227
}

/**
228
 * Ensures that a given language code is valid.
229 230 231 232 233 234 235 236 237 238 239 240 241
 *
 * Checks whether the given language is one of the enabled languages. Otherwise,
 * it returns the current, global language; or the site's default language, if
 * the additional parameter $default is TRUE.
 *
 * @param $langcode
 *   The language code to validate.
 * @param $default
 *   Whether to return the default language code or the current language code in
 *   case $langcode is invalid.
 * @return
 *   A valid language code.
 */
242 243
function field_valid_language($langcode, $default = TRUE) {
  $enabled_languages = field_content_languages();
244 245 246
  if (in_array($langcode, $enabled_languages)) {
    return $langcode;
  }
247
  global $language_content;
248
  return $default ? language_default()->language : $language_content->language;
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
}

/**
 * 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();

288 289 290
    // By default display language is set to LANGUAGE_NONE if the field
    // translation is not available. It is up to translation handlers to
    // implement language fallback rules.
291
    foreach (field_info_instances($entity_type, $bundle) as $instance) {
292
      $display_language[$instance['field_name']] = isset($entity->{$instance['field_name']}[$langcode]) ? $langcode : LANGUAGE_NONE;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
    }

    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;
312
  }
313 314

  return $display_language;
315
}