Projet

Général

Profil

Paste
Télécharger (35,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / i18n / i18n_string / i18n_string.module @ e0d35157

1
<?php
2

    
3
/**
4
 * @file
5
 * Internationalization (i18n) package - translatable strings.
6
 *
7
 * Object oriented string translation using locale and textgroups. As opposed to core locale strings,
8
 * all strings handled by this module will have a unique id (name), composed by several parts
9
 *
10
 * A string name or string id will have the form 'textgroup:type:objectid:property'. Examples:
11
 *
12
 * - 'profile:field:profile_name:title', will be the title for the profile field 'profile_name'
13
 * - 'taxonomy:term:tid:name', will be the name for the taxonomy term tid
14
 * - 'views:view_name:display_id:footer', footer text
15
 *
16
 * Notes:
17
 * - The object id must be an integer. This is intended for quick indexing of some properties
18
 *
19
 * Some concepts
20
 * - Textgroup. Group the string belongs to, defined by locale hook.
21
 * - Location. Unique id of the string for this textgroup.
22
 * - Name. Unique absolute id of the string: textgroup + location.
23
 * - Context. Object with textgroup, type, objectid, property.
24
 *
25
 * Default language
26
 * - Default language may be English or not. It will be the language set as default.
27
 *   Source strings will be stored in default language.
28
 * - In the traditional i18n use case you shouldn't change the default language once defined.
29
 *
30
 * Default language changes
31
 * - You might result in the need to change the default language at a later point.
32
 * - Enabling translation of default language will curcumvent previous limitations.
33
 * - Check i18n_string_translate_langcode() for more details.
34
 *
35
 * The API other modules to translate/update/remove user defined strings consists of
36
 *
37
 * @see i18n_string($name, $string, $langcode)
38
 * @see i18n_string_update($name, $string, $format)
39
 * @see i18n_string_remove($name, $string)
40
 *
41
 * @author Jose A. Reyero, 2007
42
 */
43

    
44
/**
45
 * Translated string is current.
46
 */
47
define('I18N_STRING_STATUS_CURRENT', 0);
48

    
49
/**
50
 * Translated string needs updating as the source has been edited.
51
 */
52
define('I18N_STRING_STATUS_UPDATE', 1);
53

    
54
/**
55
 * Source string is obsoleted, cannot be found anymore. To be deleted.
56
 */
57
define('I18N_STRING_STATUS_DELETE', 2);
58

    
59
/**
60
 * Special string formats/filters: Run through filter_xss
61
 */
62
define('I18N_STRING_FILTER_XSS', 'FILTER_XSS');
63

    
64
/**
65
 * Special string formats/filters: Run through filter_xss_admin
66
 */
67
define('I18N_STRING_FILTER_XSS_ADMIN', 'FILTER_XSS_ADMIN');
68

    
69
/**
70
 * Implements hook_help().
71
 */
72
function i18n_string_help($path, $arg) {
73
  switch ($path) {
74
    case 'admin/help#i18n_string':
75
      $output = '<p>' . t('This module adds support for other modules to translate user defined strings. Depending on which modules you have enabled that use this feature you may see different text groups to translate.') . '<p>';
76
      $output .= '<p>' . t('This works differently to Drupal standard localization system: The strings will be translated from the <a href="@configure-strings">source language</a>, which defaults to the site default language (it may not be English), so changing the default language may cause all these translations to be broken.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
77
      $output .= '<ul>';
78
      $output .= '<li>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</li>';
79
      $output .= '<li>' . t('If you are missing strings to translate, use the <a href="@refresh-strings">refresh strings</a> page.', array('@refresh-strings' => url('admin/build/translate/refresh'))) . '</li>';
80
      $output .= '</ul>';
81
      $output .= '<p>' . t('Read more on the <em>Internationalization handbook</em>: <a href="http://drupal.org/node/313293">Translating user defined strings</a>.') . '</p>';
82
      return $output;
83

    
84
    case 'admin/config/regional/translate/i18n_string':
85
      $output = '<p>' . t('On this page you can refresh and update values for user defined strings.') . '</p>';
86
      $output .= '<ul>';
87
      $output .= '<li>' . t('Use the refresh option when you are missing strings to translate for a given text group. All the strings will be re-created keeping existing translations.') . '</li>';
88
      $output .= '<li>' . t('Use the update option when some of the strings had been previously translated with the localization system, but the translations are not showing up for the configurable strings.') . '</li>';
89
      $output .= '</ul>';
90
      $output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
91
      $output .= '<p>' . t('<strong>Important:</strong> To configure which text formats are safe for translation, visit the <a href="@configure-strings">configure strings</a> page before refreshing your strings.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
92
      return $output;
93

    
94
    case 'admin/config/regional/language':
95
      $output = '<p>' . t('<strong>Warning</strong>: Changing the default language may have unwanted effects on string translations. Check also the <a href="@configure-strings">source language</a> for translations and read more about <a href="@i18n_string-help">String translation</a>', array('@configure-strings' => url('admin/config/regional/i18n/strings'), '@i18n_string-help' => url('admin/help/i18n_string'))) . '</p>';
96
      return $output;
97
    case 'admin/config/regional/i18n/strings':
98
      $output = '<p>' . t('When translating user defined strings that have a text format associated, translators will be able to edit the text before it is filtered which may be a security risk for some filters. An obvious example is when using the PHP filter but other filters may also be dangerous.') . '</p>';
99
      $output .= '<p>' . t('As a general rule <strong>do not allow any filtered text to be translated unless the translators already have access to that text format</strong>. However if you are doing all your translations through this site\'s translation UI or the Localization client, and never importing translations for other textgroups than <i>default</i>, filter access will be checked for translators on every translation page.') . '</p>';
100
      $output .= '<p>' . t('<strong>Important:</strong> After disallowing some text format, use the <a href="@refresh-strings">refresh strings</a> page so forbidden strings are deleted and not allowed anymore for translators.', array('@refresh-strings' => url('admin/config/regional/translate/i18n_string'))) . '</p>';
101
      return $output;
102
    case 'admin/config/filters':
103
      return '<p>' . t('After updating your text formats do not forget to review the list of formats allowed for string translations on the <a href="@configure-strings">configure translatable strings</a> page.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
104
  }
105
}
106

    
107
/**
108
 * Implements hook_menu().
109
 */
110
function i18n_string_menu() {
111
  $items['admin/config/regional/translate/i18n_string'] = array(
112
    'title' => 'Strings',
113
    'description' => 'Refresh user defined strings.',
114
    'weight' => 20,
115
    'type' => MENU_LOCAL_TASK,
116
    'page callback' => 'drupal_get_form',
117
    'page arguments' => array('i18n_string_admin_refresh_form'),
118
    'file' => 'i18n_string.admin.inc',
119
    'access arguments' => array('translate interface'),
120
  );
121
  $items['admin/config/regional/i18n/strings'] = array(
122
    'title' => 'Strings',
123
    'description' => 'Options for user defined strings.',
124
    'weight' => 20,
125
    'type' => MENU_LOCAL_TASK,
126
    'page callback' => 'drupal_get_form',
127
    'page arguments' => array('variable_edit_form', array('i18n_string_allowed_formats', 'i18n_string_source_language')),
128
    'access arguments' => array('administer site configuration'),
129
  );
130
  // AJAX callback path for strings.
131
  $items['i18n_string/save'] = array(
132
    'title' => 'Save string',
133
    'page callback' => 'i18n_string_l10n_client_save_string',
134
    'access arguments' => array('use on-page translation'),
135
    'file' => 'i18n_string.pages.inc',
136
    'type' => MENU_CALLBACK,
137
  );
138
  return $items;
139
}
140

    
141
/**
142
 * Implements hook_menu_alter().
143
 *
144
 * Take over the locale translation page.
145
 */
146
function i18n_string_menu_alter(&$items) {
147
  $items['admin/config/regional/translate/edit/%'] = array(
148
    'title' => 'Edit string',
149
    'page callback' => 'drupal_get_form',
150
    'page arguments' => array('i18n_string_locale_translate_edit_form', 5),
151
    'access arguments' => array('translate interface'),
152
    'file' => 'i18n_string.pages.inc',
153
    'file path' => drupal_get_path('module', 'i18n_string'),
154
  );
155
}
156

    
157
/**
158
 * Implements hook_hook_info().
159
 */
160
function i18n_string_hook_info() {
161
  $hooks['i18n_string_info'] =
162
  $hooks['i18n_string_list'] =
163
  $hooks['i18n_string_refresh'] =
164
  $hooks['i18n_string_objects'] = array(
165
    'group' => 'i18n',
166
  );
167
  return $hooks;
168
}
169

    
170
/**
171
 * Implements hook_locale().
172
 *
173
 * Provide the information from i18n_string groups to locale module
174
 */
175
function i18n_string_locale($op = 'groups') {
176
  if ($op == 'groups') {
177
    $groups = array();
178
    foreach (i18n_string_group_info() as $name => $info) {
179
      $groups[$name] = $info['title'];
180
    }
181
    return $groups;
182
  }
183
}
184

    
185
/**
186
 * Implements hook_permission().
187
 */
188
function i18n_string_permission() {
189
  return array(
190
    'translate user-defined strings' => array(
191
      'title' => t('Translate user-defined strings'),
192
      'description' => t('Translate user-defined strings that are created as part of content or configuration.'),
193
      'restrict access' => TRUE,
194
    ),
195
    'translate admin strings' => array(
196
      'title' => t('Translate admin strings'),
197
      'description' => t('Translate administrative strings with a very permissive XSS/HTML filter that allows all HTML tags.'),
198
      'restrict access' => TRUE,
199
    ),
200
  );
201
}
202

    
203
/**
204
 * Implements hook_modules_enabled().
205
 */
206
function i18n_string_modules_enabled($modules) {
207
  module_load_include('admin.inc', 'i18n_string');
208
  i18n_string_refresh_enabled_modules($modules);
209
}
210

    
211
/**
212
 * Implements hook_modules_uninstalled().
213
 */
214
function i18n_string_modules_uninstalled($modules) {
215
  module_load_include('admin.inc', 'i18n_string');
216
  i18n_string_refresh_uninstalled_modules($modules);
217
}
218

    
219
/**
220
 * Implements hook_form_FORM_ID_alter()
221
 */
222
function i18n_string_form_l10n_client_form_alter(&$form, &$form_state) {
223
  $form['#action'] = url('i18n_string/save');
224
}
225

    
226
/**
227
 * Implements hook_form_FORM_ID_alter()
228
 */
229
function i18n_string_form_locale_translate_export_po_form_alter(&$form, $form_state) {
230
  $names = locale_language_list('name', TRUE);
231
  if (i18n_string_source_language() != 'en' && array_key_exists('en', $names)) {
232
    $form['langcode']['#options']['en'] = $names['en'];
233
  }
234
}
235

    
236
/**
237
 * Implements hook_form_FORM_ID_alter()
238
 */
239
function i18n_string_form_locale_translate_import_form_alter(&$form, $form_state) {
240
  if (i18n_string_source_language() != 'en') {
241
    $names = locale_language_list('name', TRUE);
242
    if (array_key_exists('en', $names)) {
243
      $form['import']['langcode']['#options'][t('Already added languages')]['en'] = $names['en'];
244
    }
245
    else {
246
      $form['import']['langcode']['#options'][t('Languages not yet added')]['en'] = t('English');
247
    }
248
  }
249
  $form['#submit'][] = 'i18n_string_locale_translate_import_form_submit';
250
}
251

    
252
/**
253
 * Update string data after import form submit
254
 */
255
function i18n_string_locale_translate_import_form_submit($form, &$form_state) {
256
  if (!drupal_get_messages('error', FALSE) && i18n_string_group_info($form_state['values']['group'])) {
257
    i18n_string_textgroup($form_state['values']['group'])->update_check();
258
  }
259
}
260

    
261
/**
262
 * Implements hook_element_info_alter().
263
 *
264
 * We need to do this on the element info level as wysiwyg also does so and form
265
 * API (incorrectly) does not merge in the defaults for values that are arrays.
266
 */
267
function i18n_string_element_info_alter(&$types) {
268
  $types['text_format']['#pre_render'][] = 'i18n_string_pre_render_text_format';
269
}
270

    
271
/**
272
 * The '#pre_render' function to alter the text format element in a translation.
273
 * The text format for a translation is taken form the original, so the text
274
 * format drop down should be disabled.
275
 *
276
 * @param array $element
277
 *   The text_format element which will be rendered.
278
 *
279
 * @return array
280
 *   The altered text_format element with a disabled "Text format" select.
281
 */
282
function i18n_string_pre_render_text_format($element) {
283
  if (!empty($element['#i18n_string_is_translation'])) {
284
      $element['format']['format']['#attributes']['disabled'] = TRUE;
285
  }
286
  return $element;
287
}
288

    
289
/**
290
 * Check if translation is required for this language code.
291
 *
292
 * Translation is required when default language is different from the given
293
 * language, or when default language translation is explicitly enabled.
294
 *
295
 * No UI is provided to enable translation of default language. On the other
296
 * hand, you can enable/disable translation for a specific language by adding
297
 * the following to your settings.php
298
 *
299
 * @param $langcode
300
 *   Optional language code to check. It will default to current request language.
301
 * @code
302
 *   // Enable translation of specific language. Language code is 'xx'
303
 *   $conf['i18n_string_translate_langcode_xx'] = TRUE;
304
 *   // Disable translation of specific language. Language code is 'yy'
305
 *   $conf['i18n_string_translate_langcode_yy'] = FALSE;
306
 * @endcode
307
 */
308
function i18n_string_translate_langcode($langcode = NULL) {
309
  $translate = &drupal_static(__FUNCTION__);
310
  $langcode = isset($langcode) ? $langcode : i18n_langcode();
311
  if (!isset($translate[$langcode])) {
312
    $translate[$langcode] = variable_get('i18n_string_translate_langcode_' . $langcode, i18n_string_source_language() != $langcode);
313
  }
314
  return $translate[$langcode];
315
}
316

    
317
/**
318
 * Create string class from string name
319
 */
320
function i18n_string_build($name, $string = NULL) {
321
  list ($group, $context) = i18n_string_context($name);
322
  return i18n_string_textgroup($group)->build_string($context, $string);
323
}
324

    
325
/**
326
 * Update / create translation source for user defined strings.
327
 *
328
 * @param $name
329
 *   Array or string concatenated with ':' that contains textgroup and string context
330
 * @param $string
331
 *   Source string in default language. Default language may or may not be English.
332
 *   Array of key => string to update multiple strings at once
333
 * @param $options
334
 *   Array with additional options:
335
 *   - 'format', String format if the string has text format
336
 *   - 'messages', Whether to print out status messages
337
 */
338
function i18n_string_update($name, $string, $options = array()) {
339
  if (is_array($string)) {
340
    return i18n_string_multiple('update', $name, $string, $options);
341
  }
342
  else {
343
    list($textgroup, $context) = i18n_string_context($name);
344
    return i18n_string_textgroup($textgroup)->context_update($context, $string, $options);
345
  }
346
}
347

    
348
/**
349
 * Update context for strings.
350
 *
351
 * As some string locations depend on configurable values, the field needs sometimes to be updated
352
 * without losing existing translations. I.e:
353
 * - profile fields indexed by field name.
354
 * - content types indexted by low level content type name.
355
 *
356
 * Example:
357
 *  'profile:field:oldfield:*' -> 'profile:field:newfield:*'
358
 */
359
function i18n_string_update_context($oldname, $newname) {
360
  module_load_install('i18n_string');
361
  return i18n_string_install_update_context($oldname, $newname);
362
}
363

    
364
/**
365
 * Get textgroup handler.
366
 *
367
 * @return i18n_string_textgroup_default
368
 *
369
 */
370
function i18n_string_textgroup($textgroup) {
371
  $groups = &drupal_static(__FUNCTION__);
372
  if (!isset($groups[$textgroup])) {
373
    $class = i18n_string_group_info($textgroup, 'class', 'i18n_string_textgroup_default');
374
    $groups[$textgroup] = new $class($textgroup);
375
  }
376
  return $groups[$textgroup];
377
}
378

    
379
/**
380
 * Check whether a string format is allowed for translation.
381
 */
382
function i18n_string_allowed_format($format_id = NULL) {
383
  if (!$format_id || $format_id === I18N_STRING_FILTER_XSS || $format_id === I18N_STRING_FILTER_XSS_ADMIN) {
384
    return TRUE;
385
  }
386
  else {
387
    // Check the format still exists an it is in the allowed formats list.
388
    return filter_format_load($format_id) && in_array($format_id, variable_get('i18n_string_allowed_formats', array(filter_fallback_format())), TRUE);
389
  }
390
}
391

    
392
/**
393
 * Convert string name into textgroup and string context
394
 *
395
 * @param $name
396
 *   Array or string concatenated with ':' that contains textgroup and string context
397
 * @param $replace
398
 *   Parameter to replace the placeholder ('*') if we are dealing with multiple strings
399
 *   Or parameter to append to context if there's no placeholder
400
 *
401
 * @return array
402
 *   The first element will be the text group name
403
 *   The second one will be an array with string name elements, without textgroup
404
 */
405
function i18n_string_context($name, $replace = NULL) {
406
  $parts = is_array($name) ? $name : explode(':', $name);
407
  if ($replace) {
408
    $key = array_search('*', $parts);
409
    if ($key !== FALSE) {
410
      $parts[$key] = $replace;
411
    }
412
    elseif (count($parts) < 4) {
413
      array_push($parts, $replace);
414
    }
415
  }
416
  $textgroup = array_shift($parts);
417
  $context = $parts;
418
  return array($textgroup, $context);
419
}
420

    
421
/**
422
 * Mark form element as localizable
423
 */
424
function i18n_string_element_mark(&$element) {
425
  $description = '<strong>' . t('This string will be localizable. You can translate it using the <a href="@translate-interface">translate interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate/translate'))) . '</strong>';
426
  if (empty($element['#description'])) {
427
    $element['#description'] = $description;
428
  }
429
  else {
430
    $element['#description'] .= ' ' . $description;
431
  }
432
}
433

    
434

    
435
/**
436
 * Get source string object.
437
 *
438
 * This returns the i18nstring object only when it has a source.
439
 *
440
 * @return i18n_string_object
441
 */
442
function i18n_string_get_source($name) {
443
  return i18n_string_build($name)->get_source();
444
}
445

    
446
/**
447
 * Get full string object.
448
 *
449
 * Builds string and loads the source if available.
450
 *
451
 * @return i18n_string_object
452
 */
453
function i18n_string_get_string($name, $default = NULL) {
454
  $i18nstring = i18n_string_build($name, $default);
455
  $i18nstring->get_source();
456
  return $i18nstring;
457
}
458

    
459
/**
460
 * Get full string object by lid.
461
 */
462
function i18n_string_get_by_lid($lid) {
463
  $strings = i18n_string_load_multiple(array('lid' => $lid));
464
  return reset($strings);
465
}
466

    
467
/**
468
 * Load multiple strings, including string source
469
 *
470
 * @param $conditions
471
 *   Array of conditions for i18n_string table.
472
 *
473
 * @return $strings
474
 *   List of i18n string objects
475
 */
476
function i18n_string_load_multiple($conditions) {
477
  // The primary table here will be i18n_string, though we add source too.
478
  $query = db_select('i18n_string', 'i')
479
    ->fields('i');
480
  $query->leftJoin('locales_source', 's', 'i.lid = s.lid');
481
  $query->fields('s', array('source', 'version', 'location'));
482
  // Add text group condition and add conditions to the query
483
  foreach ($conditions as $field => $value) {
484
    $alias = in_array($field, array('source', 'version', 'location')) ? 's' : 'i';
485
    $query->condition($alias . '.' . $field, $value);
486
  }
487
  // this patch  is a workaround for core file bug in file
488
  // include/database/prefetch.inc (see: http://drupal.org/node/1567216)
489
  // return $query->execute()->fetchAll(PDO::FETCH_CLASS, 'i18n_string_object');
490
  $stmt = $query->execute();
491
  $stmt->setFetchMode(PDO::FETCH_CLASS, 'i18n_string_object');
492
  return $stmt->fetchAll();
493
}
494

    
495
/**
496
 * Get textgroup info, from hook_locale('info')
497
 *
498
 * @param $group
499
 *   Text group name.
500
 * @param $default
501
 *   Default value to return for a property if not set.
502
 */
503
function i18n_string_group_info($group = NULL, $property = NULL, $default = NULL) {
504
  $info = &drupal_static(__FUNCTION__ , NULL);
505

    
506
  if (!isset($info)) {
507
    $info = module_invoke_all('i18n_string_info');
508
    drupal_alter('i18n_string_info', $info);
509
  }
510

    
511
  if ($group && $property) {
512
    return isset($info[$group][$property]) ? $info[$group][$property] : $default;
513
  }
514
  elseif ($group) {
515
    return isset($info[$group]) ? $info[$group] : array();
516
  }
517
  else {
518
    return $info;
519
  }
520
}
521

    
522

    
523
/**
524
 * Translate / update multiple strings
525
 *
526
 * @param $strings
527
 *   Array of name => string pairs
528
 */
529
function i18n_string_multiple($operation, $name, $strings, $options = array()) {
530
  $result = array();
531
  // Strings may be an array of properties, we need to shift it
532
  if ($operation == 'remove') {
533
    $strings = array_flip($strings);
534
  }
535
  foreach ($strings as $key => $string) {
536
    list($textgroup, $context) = i18n_string_context($name, $key);
537
    array_unshift($context, $textgroup);
538
    $result[$key] = call_user_func('i18n_string_' . $operation, $context, $string, $options);
539
  }
540
  return $result;
541
}
542

    
543
/**
544
 * @ingroup i18napi
545
 * @{
546
 */
547

    
548
/**
549
 * Get translation for user defined string.
550
 *
551
 * This function is intended to return translations for plain strings that have NO text format
552
 *
553
 * @param array|string name
554
 *   Array or string concatenated with ':' that contains textgroup and string context
555
 * @param array|string $string
556
 *   A string in the default language, a string wth format (array with keys
557
 *   value and format),or an array of strings (without format) to be translated.
558
 * @param array $options
559
 *   An associative array of additional options, with the following keys:
560
 *   - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
561
 *   - 'filter' Filtering callback to apply to the translated string only
562
 *   - 'format' Input format to apply to the translated string only
563
 *   - 'callback' Callback to apply to the result (both to translated or untranslated string
564
 *   - 'sanitize' Whether to filter the translation applying the text format if any, default is TRUE
565
 *   - 'sanitize default' Whether to filter the default value if no translation found, default is FALSE
566
 *
567
 * @return string
568
 */
569
function i18n_string_translate($name, $string, $options = array()) {
570
  if (is_array($string) && isset($string['value'])) {
571
    $string = $string['value'];
572
  }
573
  if (is_array($string)) {
574
    return i18n_string_translate_list($name, $string, $options);
575
  }
576
  else {
577
    $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
578
    if (i18n_string_translate_langcode($langcode)) {
579
      list($textgroup, $context) = i18n_string_context($name);
580
      $translation = i18n_string_textgroup($textgroup)->context_translate($context, $string, $options);
581
      // Add for l10n client if available, we pass translation object that contains the format
582
      i18n_string_l10n_client_add($translation, $langcode);
583
      return $translation->format_translation($langcode, $options);
584
    }
585
    else {
586
      // If we don't want to translate to this language, format and return
587
      $options['sanitize'] = !empty($options['sanitize default']);
588
      return i18n_string_format($string, $options);
589
    }
590
  }
591
}
592

    
593
/**
594
 * Check user access to translate a specific string.
595
 *
596
 * If the string has a format the user is not allowed to edit, it will return FALSE
597
 *
598
 * @param $string_format;
599
 *   String object or $format_id
600
 */
601
function i18n_string_translate_access($string_format, $account = NULL) {
602
  $format_id = is_object($string_format) ? i18n_object_field($string_format, 'format') : $string_format;
603
  return user_access('translate interface', $account) &&
604
    (empty($format_id) || i18n_string_allowed_format($format_id) && ($format = filter_format_load($format_id)) && filter_access($format, $account));
605
}
606

    
607
/**
608
 * Check whether there is any problem for the  user to translate a specific string.
609
 *
610
 * Here we assume the user has 'translate interface' access that should have
611
 * been checked for the page. Possible reasons a user cannot translate a string:
612
 *
613
 * @param $i18nstring
614
 *   String object.
615
 * @param $account
616
 *   Optional user account, defaults to current user.
617
 *
618
 * @return
619
 *   None or empty string if the user has access to translate the string.
620
 *   Message if the user cannot translate that string.
621
 */
622
function i18n_string_translate_check_string($i18nstring, $account = NULL) {
623
  // Check block translation permissions.
624
  if ($i18nstring->textgroup == 'blocks') {
625
    if (!user_access('translate interface', $account) && !user_access('translate blocks', $account)) {
626
      return t('This is a user-defined string within a block. You are not allowed to translate blocks.');
627
    }
628
  }
629
  elseif (!user_access('translate interface', $account) || !user_access('translate user-defined strings', $account)) {
630
    return t('This is a user-defined string. You are not allowed to translate these strings.');
631
  }
632

    
633
  if (!empty($i18nstring->format)) {
634
    if (!i18n_string_allowed_format($i18nstring->format)) {
635
      $format = filter_format_load($i18nstring->format);
636
      return t('This string uses the %name text format. Strings with this format are not allowed for translation.', array('%name' => $format->name));
637
    }
638
    elseif ($format = filter_format_load($i18nstring->format)) {
639
      // It is a text format, check user access to that text format.
640
      if (!filter_access($format, $account)) {
641
        return t('This string uses the %name text format. You are not allowed to translate or edit texts with this format.', array('%name' => $format->name));
642
      }
643
    }
644
    else {
645
      // This is one of our special formats, I18N_STRING_FILTER_*
646
      if ($i18nstring->format == I18N_STRING_FILTER_XSS_ADMIN && !user_access('translate admin strings', $account)) {
647
        return t('The source string is an administrative string. You are not allowed to translate these strings.');
648
      }
649
    }
650
  }
651
  // No error message, it should be OK to translate.
652
  return '';
653
}
654

    
655
/**
656
 * Format the resulting translation or the default string applying callbacks
657
 *
658
 * @param $string
659
 *   Text string.
660
 * @param $options
661
 *   Array of options for string formatting:
662
 *   - 'format', text format to apply to the string, defaults to none.
663
 *   - 'sanitize', whether to apply the text format, defaults to TRUE.
664
 *   - 'cache', text format parameter.
665
 *   - 'langcode', text format parameter, defaults to current page language.
666
 *   - 'allowed_tags', allowed HTML tags when format is I18N_STRING_FILTER_XSS
667
 */
668
function i18n_string_format($string, $options = array()) {
669
  $options += array('langcode' => i18n_langcode(), 'format' => FALSE, 'sanitize' => TRUE, 'cache' => FALSE);
670
  // Apply format and callback
671
  if ($string) {
672
    if ($options['sanitize']) {
673
      if ($options['format']) {
674
        // Handle special format values (xss, xss_admin)
675
        switch ($options['format']) {
676
          case I18N_STRING_FILTER_XSS:
677
            $string = !empty($options['allowed_tags']) ? filter_xss($string, $options['allowed_tags']) : filter_xss($string);
678
            break;
679
          case I18N_STRING_FILTER_XSS_ADMIN:
680
            $string = filter_xss_admin($string);
681
            break;
682
          default:
683
            $string = check_markup($string, $options['format'], $options['langcode'], $options['cache']);
684
        }
685
      }
686
      else {
687
        $string = check_plain($string);
688
      }
689
    }
690
    if (isset($options['callback'])) {
691
      $string = call_user_func($options['callback'], $string);
692
    }
693
  }
694
  // Finally, apply prefix and suffix
695
  $options += array('prefix' => '', 'suffix' => '');
696
  return $options['prefix'] . $string . $options['suffix'];
697
}
698

    
699
/**
700
 * Get filtered translation.
701
 *
702
 * This function is intended to return translations for strings that have a text format
703
 *
704
 * @param $name
705
 *   Array or string concatenated with ':' that contains textgroup and string context
706
 * @param $default
707
 *   Default string to return if not found, already filtered
708
 * @param $options
709
 *   Array with additional options.
710
 */
711
function i18n_string_text($name, $default, $options = array()) {
712
  $options += array('format' => filter_fallback_format(), 'sanitize' => TRUE);
713
  return i18n_string_translate($name, $default, $options);
714
}
715

    
716
/**
717
 * Translation for plain string. In case it finds a translation it applies check_plain() to it
718
 *
719
 * @param $name
720
 *   Array or string concatenated with ':' that contains textgroup and string context
721
 * @param $default
722
 *   Default string to return if not found
723
 * @param $options
724
 *   Array with additional options
725
 */
726
function i18n_string_plain($name, $default, $options = array()) {
727
  $options += array('filter' => 'check_plain');
728
  return i18n_string_translate($name, $default, $options);
729
}
730

    
731
/**
732
 * Get source language code for translations
733
 */
734
function i18n_string_source_language() {
735
  return variable_get('i18n_string_source_language', language_default('language'));
736
}
737

    
738
/**
739
 * Translation for list of options
740
 *
741
 * @param $options
742
 *   Array with additional options, some changes
743
 *   - 'index' => field that will be mapped to the array key (defaults to 'property')
744
 */
745
function i18n_string_translate_list($name, $strings, $options = array()) {
746
  $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
747
  // If language is default, just return
748
  if (i18n_string_translate_langcode($langcode)) {
749
    // Get textgroup context, preserve placeholder
750
    list($textgroup, $context) = i18n_string_context($name, '*');
751
    $translations = i18n_string_textgroup($textgroup)->multiple_translate($context, $strings, $options);
752
    // Add for l10n client if available, we pass translation object that contains the format
753
    foreach ($translations as $index => $translation) {
754
      i18n_string_l10n_client_add($translation, $langcode);
755
      $strings[$index] = $translation->format_translation($langcode, $options);
756
    }
757
  }
758
  else {
759
    // Format and return
760
    foreach ($strings as $key => $string) {
761
      $strings[$key] = i18n_string_format($string, $options);
762
    }
763
  }
764
  return $strings;
765
}
766

    
767
/**
768
 * Remove source and translations for user defined string.
769
 *
770
 * Though for most strings the 'name' or 'string id' uniquely identifies that string,
771
 * there are some exceptions (like profile categories) for which we need to use the
772
 * source string itself as a search key.
773
 *
774
 * @param $name
775
 *   String name
776
 * @param $string
777
 *   Optional source string (string in default language).
778
 *   Array of string properties to remove
779
 */
780
function i18n_string_remove($name, $string = NULL, $options = array()) {
781
  if (is_array($string)) {
782
    return i18n_string_multiple('remove', $name, $string, $options);
783
  }
784
  else {
785
    list($textgroup, $context) = i18n_string_context($name);
786
    return i18n_string_textgroup($textgroup)->context_remove($context, $string, $options);
787
  }
788
}
789

    
790
/**
791
 * @} End of "ingroup i18napi".
792
 */
793

    
794
/*** l10n client related functions ***/
795

    
796
/**
797
 * Add string to l10n strings if enabled and allowed for this string
798
 *
799
 * @param $context
800
 *   String object
801
 */
802
function i18n_string_l10n_client_add($string, $langcode) {
803
  // If current language add to l10n client list for later on page translation.
804
  // If langcode translation was disabled we are not supossed to reach here.
805
  if (($langcode == i18n_langcode()) && function_exists('l10_client_add_string_to_page') && user_access('translate interface')) {
806
    if (!$string->check_translate_access()) {
807
      $translation = $string->get_translation($langcode);
808
      $source = !empty($string->source) ? $string->source : $string->string;
809
      l10_client_add_string_to_page($source, $translation ? $translation : TRUE, $string->textgroup, $string->context);
810
    }
811
  }
812
}
813

    
814
/**
815
 * Get information about object string translation
816
 */
817
function i18n_string_object_info($type = NULL, $property = NULL) {
818
  if ($type) {
819
    if (($info = i18n_object_info($type, 'string translation'))) {
820
      if ($property) {
821
        return isset($info[$property]) ? $info[$property] : NULL;
822
      }
823
      else {
824
        return $info;
825
      }
826
    }
827
  }
828
  else {
829
    $list = array();
830
    foreach (i18n_object_info() as $type => $info) {
831
      if (!empty($info['string translation'])) {
832
        $list[$type] = $info;
833
      }
834
    }
835
    return $list;
836
  }
837
}
838

    
839
/**
840
 * Implements hook_i18n_object_info_alter().
841
 *
842
 * Set a different default object wrapper for objects that have string translation.
843
 */
844
function i18n_string_i18n_object_info_alter(&$object_info) {
845
  foreach ($object_info as $type => &$info) {
846
    if (!empty($info['string translation']) && (empty($info['class']) || $info['class'] == 'i18n_object_wrapper')) {
847
      $info['class'] = 'i18n_string_object_wrapper';
848
    }
849
  }
850
}
851

    
852
/**
853
 * Translate object properties
854
 *
855
 * We clone the object previously so we don't risk translated properties being saved
856
 *
857
 * @param $type
858
 *   Object type
859
 * @param $object
860
 *   Object or array
861
 */
862
function i18n_string_object_translate($type, $object, $options = array()) {
863
  $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
864
  if (i18n_string_translate_langcode($langcode)) {
865
    // Object properties will be returned without filtering as in the original one.
866
    $options += array('sanitize' => FALSE);
867
    return i18n_object($type, $object)->translate($langcode, $options);
868
  }
869
  else {
870
    return $object;
871
  }
872
}
873

    
874
/**
875
 * Remove object strings, because object is deleted
876
 *
877
 * @param $type
878
 *   Object type
879
 * @param $object
880
 *   Object or array
881
 */
882
function i18n_string_object_remove($type, $object, $options = array()) {
883
  return i18n_object($type, $object)->strings_remove($options);
884
}
885

    
886
/**
887
 * Update object properties.
888
 *
889
 * @param $type
890
 *   Object type
891
 * @param $object
892
 *   Object or array
893
 */
894
function i18n_string_object_update($type, $object, $options = array()) {
895
  return i18n_object($type, $object)->strings_update($options);
896
}
897

    
898
/**
899
 * Generic translation page for i18n_strings objects.
900
 */
901
function i18n_string_object_translate_page($object_type, $object_value, $language = NULL) {
902
  module_load_include('inc', 'i18n_string', 'i18n_string.pages');
903
  return i18n_string_translate_page_object($object_type, $object_value, $language);
904
}
905

    
906
/**
907
 * Preload all strings for this textroup/context.
908
 *
909
 * This is a performance optimization to load all needed strings with a single query.
910
 *
911
 * Examples of valid string name to search are:
912
 *  - 'taxonomy:term:*:title'
913
 *    This will find all titles for taxonomy terms
914
 *  - array('taxonomy', 'term', array(1,2), '*')
915
 *    This will find all properties for taxonomy terms 1 and 2
916
 *
917
 * @param $name
918
 *   Specially crafted string name, it may take '*' and array parameters for each element.
919
 * @param $langcode
920
 *   Language code to search translations. Defaults to current language.
921
 *
922
 * @return array()
923
 *   String objects indexed by context.
924
 */
925
function i18n_string_translation_search($name, $langcode = NULL) {
926
  $langcode = isset($langcode) ? $langcode : i18n_langcode();
927
  list ($textgroup, $context) = i18n_string_context($name);
928
  return i18n_string_textgroup($textgroup)->multiple_translation_search($context, $langcode);
929
}
930

    
931
/**
932
 * Update / create translation for a certain source.
933
 *
934
 * @param $name
935
 *   Array or string concatenated with ':' that contains textgroup and string context
936
 * @param $translation
937
 *   Translation string for this language code
938
 * @param $langcode
939
 *   The language code to translate to a language other than what is used to display the page.
940
 * @param $source_string
941
 *   Optional source string, just in case it needs to be created.
942
 *
943
 * @return mixed
944
 *   Source string object if update was successful.
945
 *   Null if source string not found.
946
 *   FALSE if use doesn't have permission to edit this translation.
947
 */
948
function i18n_string_translation_update($name, $translation, $langcode, $source_string = NULL) {
949
  if (is_array($translation)) {
950
    return i18n_string_multiple('translation_update', $name, $translation, $langcode);
951
  }
952
  elseif ($source = i18n_string_get_source($name)) {
953
    if ($langcode == i18n_string_source_language()) {
954
      // It's the default language so we should update the string source as well.
955
      i18n_string_update($name, $translation);
956
    }
957
    else {
958
      list ($textgroup, $context) = i18n_string_context($name);
959
      i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
960
    }
961
    return $source;
962
  }
963
  elseif ($source_string) {
964
    // We don't have a source in the database, so we need to create it, but only if we've got the source too.
965
    // Note this string won't have any format.
966
    i18n_string_update($name, $source_string);
967
    return i18n_string_translation_update($name, $translation, $langcode);
968
  }
969
  else {
970
    return NULL;
971
  }
972
}
973

    
974
/**
975
 * Count operation results by result value
976
 */
977
function _i18n_string_result_count($list) {
978
  $result = array();
979
  foreach ($list as $value) {
980
    $key = (string)$value;
981
    $result[$key] = isset($result[$key]) ? $result[$key] +1 : 1;
982
  }
983
  return $result;
984
}