summaryrefslogtreecommitdiff
path: root/modules/field/field.multilingual.inc
blob: 00adf9275a6a6a65c6f0a0f7699ba3b2bafe0beb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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
288
289
290
291
292
293
294
295
296
297
298
<?php

/**
 * @file
 * Functions implementing Field API multilingual support.
 */

/**
 * @defgroup field_language Field Language API
 * @{
 * 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
 *   enabled languages with the addition of LANGUAGE_NONE. This default can be
 *   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().
 */

/**
 * Implements hook_multilingual_settings_changed().
 */
function field_multilingual_settings_changed() {
  field_info_cache_clear();
}

/**
 * Collects the available languages for the given entity type and field.
 *
 * 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.
 *
 * @return
 *   An array of valid language codes.
 */
function field_available_languages($entity_type, $field) {
  static $drupal_static_fast;
  if (!isset($drupal_static_fast)) {
    $drupal_static_fast['field_languages'] = &drupal_static(__FUNCTION__);
  }
  $field_languages = &$drupal_static_fast['field_languages'];
  $field_name = $field['field_name'];

  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 {
      $field_languages[$entity_type][$field_name] = array(LANGUAGE_NONE);
    }
  }

  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 $available_languages;
}

/**
 * Returns available content languages.
 *
 * The languages that may be associated to fields include LANGUAGE_NONE.
 *
 * @return
 *   An array of language codes.
 */
function field_content_languages() {
  return array_keys(language_list() + array(LANGUAGE_NONE => NULL));
}

/**
 * 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.
 *
 * @param $entity_type
 *   The type of the entity whose fields are to be translated.
 * @param $handler
 *   (optional) The name of the handler to be checked. Defaults to NULL.
 *
 * @return
 *   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_has_translation_handler($entity_type, $handler = NULL) {
  $entity_info = entity_get_info($entity_type);

  if (isset($handler)) {
    return !empty($entity_info['translation'][$handler]);
  }
  elseif (isset($entity_info['translation'])) {
    foreach ($entity_info['translation'] as $handler_info) {
      // The translation handler must use a non-empty data structure.
      if (!empty($handler_info)) {
        return TRUE;
      }
    }
  }

  return FALSE;
}

/**
 * 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
 * the additional parameter $default is TRUE.
 *
 * @param $langcode
 *   The language code to validate.
 * @param $default
 *   Whether to return the default language code or the current language code in
 *   case $langcode is invalid.
 * @return
 *   A valid language code.
 */
function field_valid_language($langcode, $default = TRUE) {
  $enabled_languages = field_content_languages();
  if (in_array($langcode, $enabled_languages)) {
    return $langcode;
  }
  global $language_content;
  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 if the field
    // translation is not available. 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']] = isset($entity->{$instance['field_name']}[$langcode]) ? $langcode : 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;
  }

  return $display_language;
}