Projet

Général

Profil

Paste
Télécharger (36,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / i18n / i18n_string / i18n_string.module @ 9faa5de0

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

    
122
  $items['admin/config/regional/i18n/strings'] = array(
123
    'title' => 'Strings',
124
    'description' => 'Options for user defined strings.',
125
    'weight' => 20,
126
    'type' => MENU_LOCAL_TASK,
127
    'page callback' => 'drupal_get_form',
128
    'page arguments' => array('variable_edit_form', array('i18n_string_allowed_formats', 'i18n_string_source_language', 'i18n_string_textgroup_class_[textgroup]')),
129
    'access arguments' => array('administer site configuration'),
130
  );
131
  // AJAX callback path for strings.
132
  $items['i18n_string/save'] = array(
133
    'title' => 'Save string',
134
    'page callback' => 'i18n_string_l10n_client_save_string',
135
    'access arguments' => array('use on-page translation'),
136
    'file' => 'i18n_string.pages.inc',
137
    'type' => MENU_CALLBACK,
138
  );
139
  return $items;
140
}
141

    
142
/**
143
 * Implements hook_menu_alter().
144
 *
145
 * Take over the locale translation page.
146
 */
147
function i18n_string_menu_alter(&$items) {
148
  $items['admin/config/regional/translate/edit/%'] = array(
149
    'title' => 'Edit string',
150
    'page callback' => 'drupal_get_form',
151
    'page arguments' => array('i18n_string_locale_translate_edit_form', 5),
152
    'access arguments' => array('translate interface'),
153
    'file' => 'i18n_string.pages.inc',
154
    'file path' => drupal_get_path('module', 'i18n_string'),
155
  );
156
  $items['admin/config/regional/translate/translate'] = array(
157
    'title' => 'Translate',
158
    'weight' => 10,
159
    'type' => MENU_LOCAL_TASK,
160
    'page callback' => 'i18n_string_locale_translate_seek_screen',
161
    'access arguments' => array('translate interface'),
162
    'file' => 'i18n_string.pages.inc',
163
    'file path' => drupal_get_path('module', 'i18n_string'),
164
  );
165
}
166

    
167
/**
168
 * Implements hook_hook_info().
169
 */
170
function i18n_string_hook_info() {
171
  $hooks['i18n_string_info'] =
172
  $hooks['i18n_string_list'] =
173
  $hooks['i18n_string_refresh'] =
174
  $hooks['i18n_string_objects'] = array(
175
    'group' => 'i18n',
176
  );
177
  return $hooks;
178
}
179

    
180
/**
181
 * Implements hook_locale().
182
 *
183
 * Provide the information from i18n_string groups to locale module
184
 */
185
function i18n_string_locale($op = 'groups') {
186
  if ($op == 'groups') {
187
    $groups = array();
188
    foreach (i18n_string_group_info() as $name => $info) {
189
      $groups[$name] = $info['title'];
190
    }
191
    return $groups;
192
  }
193
}
194

    
195
/**
196
 * Implements hook_permission().
197
 */
198
function i18n_string_permission() {
199
  return array(
200
    'translate user-defined strings' => array(
201
      'title' => t('Translate user-defined strings'),
202
      'description' => t('Translate user-defined strings that are created as part of content or configuration.'),
203
      'restrict access' => TRUE,
204
    ),
205
    'translate admin strings' => array(
206
      'title' => t('Translate admin strings'),
207
      'description' => t('Translate administrative strings with a very permissive XSS/HTML filter that allows all HTML tags.'),
208
      'restrict access' => TRUE,
209
    ),
210
  );
211
}
212

    
213
/**
214
 * Implements hook_modules_enabled().
215
 */
216
function i18n_string_modules_enabled($modules) {
217
  module_load_include('admin.inc', 'i18n_string');
218
  i18n_string_refresh_enabled_modules($modules);
219
}
220

    
221
/**
222
 * Implements hook_modules_uninstalled().
223
 */
224
function i18n_string_modules_uninstalled($modules) {
225
  module_load_include('admin.inc', 'i18n_string');
226
  i18n_string_refresh_uninstalled_modules($modules);
227
}
228

    
229
/**
230
 * Implements hook_form_FORM_ID_alter()
231
 */
232
function i18n_string_form_l10n_client_form_alter(&$form, &$form_state) {
233
  $form['#action'] = url('i18n_string/save');
234
}
235

    
236
/**
237
 * Implements hook_form_FORM_ID_alter()
238
 */
239
function i18n_string_form_locale_translate_export_po_form_alter(&$form, $form_state) {
240
  $names = locale_language_list('name', TRUE);
241
  if (i18n_string_source_language() != 'en' && array_key_exists('en', $names)) {
242
    $form['langcode']['#options']['en'] = $names['en'];
243
  }
244
}
245

    
246
/**
247
 * Implements hook_form_FORM_ID_alter()
248
 */
249
function i18n_string_form_locale_translate_import_form_alter(&$form, $form_state) {
250
  if (i18n_string_source_language() != 'en') {
251
    $names = locale_language_list('name', TRUE);
252
    if (array_key_exists('en', $names)) {
253
      $form['import']['langcode']['#options'][t('Already added languages')]['en'] = $names['en'];
254
    }
255
    else {
256
      $form['import']['langcode']['#options'][t('Languages not yet added')]['en'] = t('English');
257
    }
258
  }
259
  $form['#submit'][] = 'i18n_string_locale_translate_import_form_submit';
260
}
261

    
262
/**
263
 * Update string data after import form submit
264
 */
265
function i18n_string_locale_translate_import_form_submit($form, &$form_state) {
266
  if (!drupal_get_messages('error', FALSE) && i18n_string_group_info($form_state['values']['group'])) {
267
    i18n_string_textgroup($form_state['values']['group'])->update_check();
268
  }
269
}
270

    
271
/**
272
 * Implements hook_element_info_alter().
273
 *
274
 * We need to do this on the element info level as wysiwyg also does so and form
275
 * API (incorrectly) does not merge in the defaults for values that are arrays.
276
 */
277
function i18n_string_element_info_alter(&$types) {
278
  $types['text_format']['#pre_render'][] = 'i18n_string_pre_render_text_format';
279
}
280

    
281
/**
282
 * The '#pre_render' function to alter the text format element in a translation.
283
 * The text format for a translation is taken form the original, so the text
284
 * format drop down should be disabled.
285
 *
286
 * @param array $element
287
 *   The text_format element which will be rendered.
288
 *
289
 * @return array
290
 *   The altered text_format element with a disabled "Text format" select.
291
 */
292
function i18n_string_pre_render_text_format($element) {
293
  if (!empty($element['#i18n_string_is_translation'])) {
294
      $element['format']['format']['#attributes']['disabled'] = TRUE;
295
  }
296
  return $element;
297
}
298

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

    
327
/**
328
 * Create string class from string name
329
 */
330
function i18n_string_build($name, $string = NULL) {
331
  list ($group, $context) = i18n_string_context($name);
332
  return i18n_string_textgroup($group)->build_string($context, $string);
333
}
334

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

    
358
/**
359
 * Update context for strings.
360
 *
361
 * As some string locations depend on configurable values, the field needs sometimes to be updated
362
 * without losing existing translations. I.e:
363
 * - profile fields indexed by field name.
364
 * - content types indexted by low level content type name.
365
 *
366
 * Example:
367
 *  'profile:field:oldfield:*' -> 'profile:field:newfield:*'
368
 */
369
function i18n_string_update_context($oldname, $newname) {
370
  module_load_install('i18n_string');
371
  return i18n_string_install_update_context($oldname, $newname);
372
}
373

    
374
/**
375
 * Get textgroup handler.
376
 *
377
 * @return i18n_string_textgroup_default
378
 *
379
 */
380
function i18n_string_textgroup($textgroup) {
381
  $groups = &drupal_static(__FUNCTION__);
382
  if (!isset($groups[$textgroup])) {
383
    $class = i18n_string_group_info($textgroup, 'class', 'i18n_string_textgroup_default');
384
    $groups[$textgroup] = new $class($textgroup);
385
  }
386
  return $groups[$textgroup];
387
}
388

    
389
/**
390
 * Check whether a string format is allowed for translation.
391
 */
392
function i18n_string_allowed_format($format_id = NULL) {
393
  if (!$format_id || $format_id === I18N_STRING_FILTER_XSS || $format_id === I18N_STRING_FILTER_XSS_ADMIN) {
394
    return TRUE;
395
  }
396
  else {
397
    // Check the format still exists an it is in the allowed formats list.
398
    return filter_format_load($format_id) && in_array($format_id, variable_get('i18n_string_allowed_formats', array(filter_fallback_format())), TRUE);
399
  }
400
}
401

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

    
431
/**
432
 * Mark form element as localizable
433
 */
434
function i18n_string_element_mark(&$element) {
435
  $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>';
436
  if (empty($element['#description'])) {
437
    $element['#description'] = $description;
438
  }
439
  else {
440
    $element['#description'] .= ' ' . $description;
441
  }
442
}
443

    
444

    
445
/**
446
 * Get source string object.
447
 *
448
 * This returns the i18nstring object only when it has a source.
449
 *
450
 * @return i18n_string_object
451
 */
452
function i18n_string_get_source($name) {
453
  return i18n_string_build($name)->get_source();
454
}
455

    
456
/**
457
 * Get full string object.
458
 *
459
 * Builds string and loads the source if available.
460
 *
461
 * @return i18n_string_object
462
 */
463
function i18n_string_get_string($name, $default = NULL) {
464
  $i18nstring = i18n_string_build($name, $default);
465
  $i18nstring->get_source();
466
  return $i18nstring;
467
}
468

    
469
/**
470
 * Get full string object by lid.
471
 */
472
function i18n_string_get_by_lid($lid) {
473
  $strings = i18n_string_load_multiple(array('lid' => $lid));
474
  return reset($strings);
475
}
476

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

    
505
/**
506
 * Get textgroup info, from hook_locale('info')
507
 *
508
 * @param $group
509
 *   Text group name.
510
 * @param $default
511
 *   Default value to return for a property if not set.
512
 */
513
function i18n_string_group_info($group = NULL, $property = NULL, $default = NULL) {
514
  $info = &drupal_static(__FUNCTION__ , NULL);
515

    
516
  if (!isset($info)) {
517
    $info = module_invoke_all('i18n_string_info');
518
    drupal_alter('i18n_string_info', $info);
519
  }
520

    
521
  if ($group && $property) {
522
    return isset($info[$group][$property]) ? $info[$group][$property] : $default;
523
  }
524
  elseif ($group) {
525
    return isset($info[$group]) ? $info[$group] : array();
526
  }
527
  else {
528
    return $info;
529
  }
530
}
531

    
532
/**
533
 * Implements hook_i18n_string_info_alter().
534
 *
535
 * Set determined classes to use for the text group.
536
 */
537
function i18n_string_i18n_string_info_alter(&$info) {
538
  foreach (array_keys($info) as $name) {
539
    // If class is not defined. Classes from other modules, fixed classes and etc.
540
    if (!isset($info[$name]['class'])) {
541
      $info[$name]['class'] = variable_get('i18n_string_textgroup_class_' . $name, 'i18n_string_textgroup_default');
542
    }
543
  }
544
}
545

    
546
/**
547
 * Translate / update multiple strings
548
 *
549
 * @param $strings
550
 *   Array of name => string pairs
551
 */
552
function i18n_string_multiple($operation, $name, $strings, $options = array()) {
553
  $result = array();
554
  // Strings may be an array of properties, we need to shift it
555
  if ($operation == 'remove') {
556
    $strings = array_flip($strings);
557
  }
558
  foreach ($strings as $key => $string) {
559
    list($textgroup, $context) = i18n_string_context($name, $key);
560
    array_unshift($context, $textgroup);
561
    $result[$key] = call_user_func('i18n_string_' . $operation, $context, $string, $options);
562
  }
563
  return $result;
564
}
565

    
566
/**
567
 * @ingroup i18napi
568
 * @{
569
 */
570

    
571
/**
572
 * Get translation for user defined string.
573
 *
574
 * This function is intended to return translations for plain strings that have NO text format
575
 *
576
 * @param array|string name
577
 *   Array or string concatenated with ':' that contains textgroup and string context
578
 * @param array|string $string
579
 *   A string in the default language, a string wth format (array with keys
580
 *   value and format),or an array of strings (without format) to be translated.
581
 * @param array $options
582
 *   An associative array of additional options, with the following keys:
583
 *   - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
584
 *   - 'filter' Filtering callback to apply to the translated string only
585
 *   - 'format' Input format to apply to the translated string only
586
 *   - 'callback' Callback to apply to the result (both to translated or untranslated string
587
 *   - 'sanitize' Whether to filter the translation applying the text format if any, default is TRUE
588
 *   - 'sanitize default' Whether to filter the default value if no translation found, default is FALSE
589
 *
590
 * @return string
591
 */
592
function i18n_string_translate($name, $string, $options = array()) {
593
  if (is_array($string) && isset($string['value'])) {
594
    $string = $string['value'];
595
  }
596
  if (is_array($string)) {
597
    return i18n_string_translate_list($name, $string, $options);
598
  }
599
  else {
600
    $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
601
    if (i18n_string_translate_langcode($langcode)) {
602
      list($textgroup, $context) = i18n_string_context($name);
603
      $translation = i18n_string_textgroup($textgroup)->context_translate($context, $string, $options);
604
      // Add for l10n client if available, we pass translation object that contains the format
605
      i18n_string_l10n_client_add($translation, $langcode);
606
      return $translation->format_translation($langcode, $options);
607
    }
608
    else {
609
      // If we don't want to translate to this language, format and return
610
      $options['sanitize'] = !empty($options['sanitize default']);
611
      return i18n_string_format($string, $options);
612
    }
613
  }
614
}
615

    
616
/**
617
 * Check user access to translate a specific string.
618
 *
619
 * If the string has a format the user is not allowed to edit, it will return FALSE
620
 *
621
 * @param $string_format;
622
 *   String object or $format_id
623
 */
624
function i18n_string_translate_access($string_format, $account = NULL) {
625
  $format_id = is_object($string_format) ? i18n_object_field($string_format, 'format') : $string_format;
626
  return user_access('translate interface', $account) &&
627
    (empty($format_id) || i18n_string_allowed_format($format_id) && ($format = filter_format_load($format_id)) && filter_access($format, $account));
628
}
629

    
630
/**
631
 * Check whether there is any problem for the  user to translate a specific string.
632
 *
633
 * Here we assume the user has 'translate interface' access that should have
634
 * been checked for the page. Possible reasons a user cannot translate a string:
635
 *
636
 * @param $i18nstring
637
 *   String object.
638
 * @param $account
639
 *   Optional user account, defaults to current user.
640
 *
641
 * @return
642
 *   None or empty string if the user has access to translate the string.
643
 *   Message if the user cannot translate that string.
644
 */
645
function i18n_string_translate_check_string($i18nstring, $account = NULL) {
646
  // Check block translation permissions.
647
  if ($i18nstring->textgroup == 'blocks') {
648
    if (!user_access('translate interface', $account) && !user_access('translate blocks', $account)) {
649
      return t('This is a user-defined string within a block. You are not allowed to translate blocks.');
650
    }
651
  }
652
  elseif (!user_access('translate interface', $account) || !user_access('translate user-defined strings', $account)) {
653
    return t('This is a user-defined string. You are not allowed to translate these strings.');
654
  }
655

    
656
  if (!empty($i18nstring->format)) {
657
    if (!i18n_string_allowed_format($i18nstring->format)) {
658
      $format = filter_format_load($i18nstring->format);
659
      return t('This string uses the %name text format. Strings with this format are not allowed for translation.', array('%name' => $format->name));
660
    }
661
    elseif ($format = filter_format_load($i18nstring->format)) {
662
      // It is a text format, check user access to that text format.
663
      if (!filter_access($format, $account)) {
664
        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));
665
      }
666
    }
667
    else {
668
      // This is one of our special formats, I18N_STRING_FILTER_*
669
      if ($i18nstring->format == I18N_STRING_FILTER_XSS_ADMIN && !user_access('translate admin strings', $account)) {
670
        return t('The source string is an administrative string. You are not allowed to translate these strings.');
671
      }
672
    }
673
  }
674
  // No error message, it should be OK to translate.
675
  return '';
676
}
677

    
678
/**
679
 * Format the resulting translation or the default string applying callbacks
680
 *
681
 * @param $string
682
 *   Text string.
683
 * @param $options
684
 *   Array of options for string formatting:
685
 *   - 'format', text format to apply to the string, defaults to none.
686
 *   - 'sanitize', whether to apply the text format, defaults to TRUE.
687
 *   - 'cache', text format parameter.
688
 *   - 'langcode', text format parameter, defaults to current page language.
689
 *   - 'allowed_tags', allowed HTML tags when format is I18N_STRING_FILTER_XSS
690
 */
691
function i18n_string_format($string, $options = array()) {
692
  $options += array('langcode' => i18n_langcode(), 'format' => FALSE, 'sanitize' => TRUE, 'cache' => FALSE);
693
  // Apply format and callback
694
  if ($string) {
695
    if ($options['sanitize']) {
696
      if ($options['format']) {
697
        // Handle special format values (xss, xss_admin)
698
        switch ($options['format']) {
699
          case I18N_STRING_FILTER_XSS:
700
            $string = !empty($options['allowed_tags']) ? filter_xss($string, $options['allowed_tags']) : filter_xss($string);
701
            break;
702
          case I18N_STRING_FILTER_XSS_ADMIN:
703
            $string = filter_xss_admin($string);
704
            break;
705
          default:
706
            $string = check_markup($string, $options['format'], $options['langcode'], $options['cache']);
707
        }
708
      }
709
      else {
710
        $string = check_plain($string);
711
      }
712
    }
713
    if (isset($options['callback'])) {
714
      $string = call_user_func($options['callback'], $string);
715
    }
716
  }
717
  // Finally, apply prefix and suffix
718
  $options += array('prefix' => '', 'suffix' => '');
719
  return $options['prefix'] . $string . $options['suffix'];
720
}
721

    
722
/**
723
 * Get filtered translation.
724
 *
725
 * This function is intended to return translations for strings that have a text format
726
 *
727
 * @param $name
728
 *   Array or string concatenated with ':' that contains textgroup and string context
729
 * @param $default
730
 *   Default string to return if not found, already filtered
731
 * @param $options
732
 *   Array with additional options.
733
 */
734
function i18n_string_text($name, $default, $options = array()) {
735
  $options += array('format' => filter_fallback_format(), 'sanitize' => TRUE);
736
  return i18n_string_translate($name, $default, $options);
737
}
738

    
739
/**
740
 * Translation for plain string. In case it finds a translation it applies check_plain() to it
741
 *
742
 * @param $name
743
 *   Array or string concatenated with ':' that contains textgroup and string context
744
 * @param $default
745
 *   Default string to return if not found
746
 * @param $options
747
 *   Array with additional options
748
 */
749
function i18n_string_plain($name, $default, $options = array()) {
750
  $options += array('filter' => 'check_plain');
751
  return i18n_string_translate($name, $default, $options);
752
}
753

    
754
/**
755
 * Get source language code for translations
756
 */
757
function i18n_string_source_language() {
758
  return variable_get('i18n_string_source_language', language_default('language'));
759
}
760

    
761
/**
762
 * Translation for list of options
763
 *
764
 * @param $options
765
 *   Array with additional options, some changes
766
 *   - 'index' => field that will be mapped to the array key (defaults to 'property')
767
 */
768
function i18n_string_translate_list($name, $strings, $options = array()) {
769
  $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
770
  // If language is default, just return
771
  if (i18n_string_translate_langcode($langcode)) {
772
    // Get textgroup context, preserve placeholder
773
    list($textgroup, $context) = i18n_string_context($name, '*');
774
    $translations = i18n_string_textgroup($textgroup)->multiple_translate($context, $strings, $options);
775
    // Add for l10n client if available, we pass translation object that contains the format
776
    foreach ($translations as $index => $translation) {
777
      i18n_string_l10n_client_add($translation, $langcode);
778
      $strings[$index] = $translation->format_translation($langcode, $options);
779
    }
780
  }
781
  else {
782
    // Format and return
783
    foreach ($strings as $key => $string) {
784
      $strings[$key] = i18n_string_format($string, $options);
785
    }
786
  }
787
  return $strings;
788
}
789

    
790
/**
791
 * Remove source and translations for user defined string.
792
 *
793
 * Though for most strings the 'name' or 'string id' uniquely identifies that string,
794
 * there are some exceptions (like profile categories) for which we need to use the
795
 * source string itself as a search key.
796
 *
797
 * @param $name
798
 *   String name
799
 * @param $string
800
 *   Optional source string (string in default language).
801
 *   Array of string properties to remove
802
 */
803
function i18n_string_remove($name, $string = NULL, $options = array()) {
804
  if (is_array($string)) {
805
    return i18n_string_multiple('remove', $name, $string, $options);
806
  }
807
  else {
808
    list($textgroup, $context) = i18n_string_context($name);
809
    return i18n_string_textgroup($textgroup)->context_remove($context, $string, $options);
810
  }
811
}
812

    
813
/**
814
 * @} End of "ingroup i18napi".
815
 */
816

    
817
/*** l10n client related functions ***/
818

    
819
/**
820
 * Add string to l10n strings if enabled and allowed for this string
821
 *
822
 * @param $context
823
 *   String object
824
 */
825
function i18n_string_l10n_client_add($string, $langcode) {
826
  // If current language add to l10n client list for later on page translation.
827
  // If langcode translation was disabled we are not supossed to reach here.
828
  if (($langcode == i18n_langcode()) && function_exists('l10_client_add_string_to_page') && user_access('translate interface')) {
829
    if (!$string->check_translate_access()) {
830
      $translation = $string->get_translation($langcode);
831
      $source = !empty($string->source) ? $string->source : $string->string;
832
      l10_client_add_string_to_page($source, $translation ? $translation : TRUE, $string->textgroup, $string->context);
833
    }
834
  }
835
}
836

    
837
/**
838
 * Get information about object string translation
839
 */
840
function i18n_string_object_info($type = NULL, $property = NULL) {
841
  if ($type) {
842
    if (($info = i18n_object_info($type, 'string translation'))) {
843
      if ($property) {
844
        return isset($info[$property]) ? $info[$property] : NULL;
845
      }
846
      else {
847
        return $info;
848
      }
849
    }
850
  }
851
  else {
852
    $list = array();
853
    foreach (i18n_object_info() as $type => $info) {
854
      if (!empty($info['string translation'])) {
855
        $list[$type] = $info;
856
      }
857
    }
858
    return $list;
859
  }
860
}
861

    
862
/**
863
 * Implements hook_i18n_object_info_alter().
864
 *
865
 * Set a different default object wrapper for objects that have string translation.
866
 */
867
function i18n_string_i18n_object_info_alter(&$object_info) {
868
  foreach ($object_info as $type => &$info) {
869
    if (!empty($info['string translation']) && (empty($info['class']) || $info['class'] == 'i18n_object_wrapper')) {
870
      $info['class'] = 'i18n_string_object_wrapper';
871
    }
872
  }
873
}
874

    
875
/**
876
 * Translate object properties
877
 *
878
 * We clone the object previously so we don't risk translated properties being saved
879
 *
880
 * @param $type
881
 *   Object type
882
 * @param $object
883
 *   Object or array
884
 */
885
function i18n_string_object_translate($type, $object, $options = array()) {
886
  $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
887
  if (i18n_string_translate_langcode($langcode)) {
888
    // Object properties will be returned without filtering as in the original one.
889
    $options += array('sanitize' => FALSE);
890
    return i18n_object($type, $object)->translate($langcode, $options);
891
  }
892
  else {
893
    return $object;
894
  }
895
}
896

    
897
/**
898
 * Remove object strings, because object is deleted
899
 *
900
 * @param $type
901
 *   Object type
902
 * @param $object
903
 *   Object or array
904
 */
905
function i18n_string_object_remove($type, $object, $options = array()) {
906
  return i18n_object($type, $object)->strings_remove($options);
907
}
908

    
909
/**
910
 * Update object properties.
911
 *
912
 * @param $type
913
 *   Object type
914
 * @param $object
915
 *   Object or array
916
 */
917
function i18n_string_object_update($type, $object, $options = array()) {
918
  return i18n_object($type, $object)->strings_update($options);
919
}
920

    
921
/**
922
 * Generic translation page for i18n_strings objects.
923
 */
924
function i18n_string_object_translate_page($object_type, $object_value, $language = NULL) {
925
  module_load_include('inc', 'i18n_string', 'i18n_string.pages');
926
  return i18n_string_translate_page_object($object_type, $object_value, $language);
927
}
928

    
929
/**
930
 * Preload all strings for this textroup/context.
931
 *
932
 * This is a performance optimization to load all needed strings with a single query.
933
 *
934
 * Examples of valid string name to search are:
935
 *  - 'taxonomy:term:*:title'
936
 *    This will find all titles for taxonomy terms
937
 *  - array('taxonomy', 'term', array(1,2), '*')
938
 *    This will find all properties for taxonomy terms 1 and 2
939
 *
940
 * @param $name
941
 *   Specially crafted string name, it may take '*' and array parameters for each element.
942
 * @param $langcode
943
 *   Language code to search translations. Defaults to current language.
944
 *
945
 * @return array()
946
 *   String objects indexed by context.
947
 */
948
function i18n_string_translation_search($name, $langcode = NULL) {
949
  $langcode = isset($langcode) ? $langcode : i18n_langcode();
950
  list ($textgroup, $context) = i18n_string_context($name);
951
  return i18n_string_textgroup($textgroup)->multiple_translation_search($context, $langcode);
952
}
953

    
954
/**
955
 * Update / create translation for a certain source.
956
 *
957
 * @param $name
958
 *   Array or string concatenated with ':' that contains textgroup and string context
959
 * @param $translation
960
 *   Translation string for this language code
961
 * @param $langcode
962
 *   The language code to translate to a language other than what is used to display the page.
963
 * @param $source_string
964
 *   Optional source string, just in case it needs to be created.
965
 *
966
 * @return mixed
967
 *   Source string object if update was successful.
968
 *   Null if source string not found.
969
 *   FALSE if use doesn't have permission to edit this translation.
970
 */
971
function i18n_string_translation_update($name, $translation, $langcode, $source_string = NULL) {
972
  if (is_array($translation)) {
973
    return i18n_string_multiple('translation_update', $name, $translation, $langcode);
974
  }
975
  elseif ($source = i18n_string_get_source($name)) {
976
    if ($langcode == i18n_string_source_language()) {
977
      // It's the default language so we should update the string source as well.
978
      i18n_string_update($name, $translation);
979
    }
980
    else {
981
      list ($textgroup, $context) = i18n_string_context($name);
982
      i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
983
    }
984
    return $source;
985
  }
986
  elseif ($source_string) {
987
    // We don't have a source in the database, so we need to create it, but only if we've got the source too.
988
    // Note this string won't have any format.
989
    i18n_string_update($name, $source_string);
990
    return i18n_string_translation_update($name, $translation, $langcode);
991
  }
992
  else {
993
    return NULL;
994
  }
995
}
996

    
997
/**
998
 * Count operation results by result value
999
 */
1000
function _i18n_string_result_count($list) {
1001
  $result = array();
1002
  foreach ($list as $value) {
1003
    $key = (string)$value;
1004
    $result[$key] = isset($result[$key]) ? $result[$key] +1 : 1;
1005
  }
1006
  return $result;
1007
}