Projet

Général

Profil

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

root / drupal7 / sites / all / modules / l10n_update / l10n_update.locale.inc @ 64ad485a

1
<?php
2

    
3
/**
4
 * @file
5
 * Override part of locale.inc library so we can manage string status
6
 */
7

    
8
/**
9
 * Parses Gettext Portable Object file information and inserts into database
10
 *
11
 * This is an improved version of _locale_import_po() to handle translation status
12
 *
13
 * @param $file
14
 *   Drupal file object corresponding to the PO file to import
15
 * @param $langcode
16
 *   Language code
17
 * @param $mode
18
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
19
 * @param $group
20
 *   Text group to import PO file into (eg. 'default' for interface translations)
21
 *
22
 * @return boolean
23
 *   Result array on success. FALSE on failure
24
 */
25
function _l10n_update_locale_import_po($file, $langcode, $mode, $group = NULL) {
26
  // Try to allocate enough time to parse and import the data.
27
  drupal_set_time_limit(240);
28

    
29
  // Check if we have the language already in the database.
30
  if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) {
31
    drupal_set_message(t('The language selected for import is not supported.'), 'error');
32
    return FALSE;
33
  }
34

    
35
  // Get strings from file (returns on failure after a partial import, or on success)
36
  $status = _l10n_update_locale_import_read_po('db-store', $file, $mode, $langcode, $group);
37
  if ($status === FALSE) {
38
    // Error messages are set in _locale_import_read_po().
39
    return FALSE;
40
  }
41

    
42
  // Get status information on import process.
43
  list($header_done, $additions, $updates, $deletes, $skips) = _l10n_update_locale_import_one_string('db-report');
44

    
45
  if (!$header_done) {
46
    drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
47
  }
48

    
49
  // Clear cache and force refresh of JavaScript translations.
50
  _locale_invalidate_js($langcode);
51
  cache_clear_all('locale:', 'cache', TRUE);
52

    
53
  // Rebuild the menu, strings may have changed.
54
  menu_rebuild();
55

    
56
  watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
57
  if ($skips) {
58
    watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
59
  }
60

    
61
  // Return results of this import.
62
  return array(
63
    'file' => $file,
64
    'language' => $langcode,
65
    'add' => $additions,
66
    'update' => $updates,
67
    'delete' => $deletes,
68
    'skip' => $skips,
69
  );
70
}
71

    
72
/**
73
 * Parses Gettext Portable Object file into an array
74
 *
75
 * @param $op
76
 *   Storage operation type: db-store or mem-store
77
 * @param $file
78
 *   Drupal file object corresponding to the PO file to import
79
 * @param $mode
80
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
81
 * @param $lang
82
 *   Language code
83
 * @param $group
84
 *   Text group to import PO file into (eg. 'default' for interface translations)
85
 */
86
function _l10n_update_locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
87

    
88
  $fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
89
  if (!$fd) {
90
    _locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
91
    return FALSE;
92
  }
93

    
94
  $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
95
  $current = array();   // Current entry being read
96
  $plural = 0;          // Current plural form
97
  $lineno = 0;          // Current line
98

    
99
  while (!feof($fd)) {
100
    $line = fgets($fd, 10*1024); // A line should not be this long
101
    if ($lineno == 0) {
102
      // The first line might come with a UTF-8 BOM, which should be removed.
103
      $line = str_replace("\xEF\xBB\xBF", '', $line);
104
    }
105
    $lineno++;
106
    $line = trim(strtr($line, array("\\\n" => "")));
107

    
108
    if (!strncmp("#", $line, 1)) { // A comment
109
      if ($context == "COMMENT") { // Already in comment context: add
110
        $current["#"][] = substr($line, 1);
111
      }
112
      elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
113
        _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
114
        $current = array();
115
        $current["#"][] = substr($line, 1);
116
        $context = "COMMENT";
117
      }
118
      else { // Parse error
119
        _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
120
        return FALSE;
121
      }
122
    }
123
    elseif (!strncmp("msgid_plural", $line, 12)) {
124
      if ($context != "MSGID") { // Must be plural form for current entry
125
        _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
126
        return FALSE;
127
      }
128
      $line = trim(substr($line, 12));
129
      $quoted = _locale_import_parse_quoted($line);
130
      if ($quoted === FALSE) {
131
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
132
        return FALSE;
133
      }
134
      $current["msgid"] = $current["msgid"] . "\0" . $quoted;
135
      $context = "MSGID_PLURAL";
136
    }
137
    elseif (!strncmp("msgid", $line, 5)) {
138
      if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {   // End current entry, start a new one
139
        _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
140
        $current = array();
141
      }
142
      elseif ($context == "MSGID") { // Already in this context? Parse error
143
        _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno);
144
        return FALSE;
145
      }
146
      $line = trim(substr($line, 5));
147
      $quoted = _locale_import_parse_quoted($line);
148
      if ($quoted === FALSE) {
149
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
150
        return FALSE;
151
      }
152
      $current["msgid"] = $quoted;
153
      $context = "MSGID";
154
    }
155
    elseif (!strncmp("msgctxt", $line, 7)) {
156
      if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {   // End current entry, start a new one
157
        _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
158
        $current = array();
159
      }
160
      elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
161
        _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno);
162
        return FALSE;
163
      }
164
      $line = trim(substr($line, 7));
165
      $quoted = _locale_import_parse_quoted($line);
166
      if ($quoted === FALSE) {
167
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
168
        return FALSE;
169
      }
170
      $current["msgctxt"] = $quoted;
171
      $context = "MSGCTXT";
172
    }
173
    elseif (!strncmp("msgstr[", $line, 7)) {
174
      if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
175
        _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
176
        return FALSE;
177
      }
178
      if (strpos($line, "]") === FALSE) {
179
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
180
        return FALSE;
181
      }
182
      $frombracket = strstr($line, "[");
183
      $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
184
      $line = trim(strstr($line, " "));
185
      $quoted = _locale_import_parse_quoted($line);
186
      if ($quoted === FALSE) {
187
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
188
        return FALSE;
189
      }
190
      $current["msgstr"][$plural] = $quoted;
191
      $context = "MSGSTR_ARR";
192
    }
193
    elseif (!strncmp("msgstr", $line, 6)) {
194
      if (($context != "MSGID") && ($context != "MSGCTXT")) {   // Should come just after a msgid or msgctxt block
195
        _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno);
196
        return FALSE;
197
      }
198
      $line = trim(substr($line, 6));
199
      $quoted = _locale_import_parse_quoted($line);
200
      if ($quoted === FALSE) {
201
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
202
        return FALSE;
203
      }
204
      $current["msgstr"] = $quoted;
205
      $context = "MSGSTR";
206
    }
207
    elseif ($line != "") {
208
      $quoted = _locale_import_parse_quoted($line);
209
      if ($quoted === FALSE) {
210
        _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
211
        return FALSE;
212
      }
213
      if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
214
        $current["msgid"] .= $quoted;
215
      }
216
      elseif ($context == "MSGCTXT") {
217
        $current["msgctxt"] .= $quoted;
218
      }
219
      elseif ($context == "MSGSTR") {
220
        $current["msgstr"] .= $quoted;
221
      }
222
      elseif ($context == "MSGSTR_ARR") {
223
        $current["msgstr"][$plural] .= $quoted;
224
      }
225
      else {
226
        _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno);
227
        return FALSE;
228
      }
229
    }
230
  }
231

    
232
  // End of PO file, flush last entry.
233
  if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
234
    _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
235
  }
236
  elseif ($context != "COMMENT") {
237
    _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
238
    return FALSE;
239
  }
240

    
241
}
242

    
243
/**
244
 * Imports a string into the database
245
 *
246
 * @param $op
247
 *   Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'
248
 * @param $value
249
 *   Details of the string stored
250
 * @param $mode
251
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
252
 * @param $lang
253
 *   Language to store the string in
254
 * @param $file
255
 *   Object representation of file being imported, only required when op is 'db-store'
256
 * @param $group
257
 *   Text group to import PO file into (eg. 'default' for interface translations)
258
 */
259
function _l10n_update_locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') {
260
  $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
261
  $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
262
  $strings = &drupal_static(__FUNCTION__ . ':strings', array());
263

    
264
  switch ($op) {
265
    // Return stored strings
266
    case 'mem-report':
267
      return $strings;
268

    
269
    // Store string in memory (only supports single strings)
270
    case 'mem-store':
271
      $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr'];
272
      return;
273

    
274
    // Called at end of import to inform the user
275
    case 'db-report':
276
      return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']);
277

    
278
    // Store the string we got in the database.
279
    case 'db-store':
280
      // We got header information.
281
      if ($value['msgid'] == '') {
282
        $languages = language_list();
283
        if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) {
284
          // Since we only need to parse the header if we ought to update the
285
          // plural formula, only run this if we don't need to keep existing
286
          // data untouched or if we don't have an existing plural formula.
287
          $header = _locale_import_parse_header($value['msgstr']);
288

    
289
          // Get the plural formula and update in database.
290
          if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) {
291
            list($nplurals, $plural) = $p;
292
            db_update('languages')
293
              ->fields(array(
294
                'plurals' => $nplurals,
295
                'formula' => $plural,
296
              ))
297
              ->condition('language', $lang)
298
              ->execute();
299
          }
300
          else {
301
            db_update('languages')
302
              ->fields(array(
303
                'plurals' => 0,
304
                'formula' => '',
305
              ))
306
              ->condition('language', $lang)
307
              ->execute();
308
          }
309
        }
310
        $header_done = TRUE;
311
      }
312

    
313
      else {
314
        // Some real string to import.
315
        $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
316

    
317
        if (strpos($value['msgid'], "\0")) {
318
          // This string has plural versions.
319
          $english = explode("\0", $value['msgid'], 2);
320
          $entries = array_keys($value['msgstr']);
321
          for ($i = 3; $i <= count($entries); $i++) {
322
            $english[] = $english[1];
323
          }
324
          $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
325
          $english = array_map('_locale_import_append_plural', $english, $entries);
326
          foreach ($translation as $key => $trans) {
327
            if ($key == 0) {
328
              $plid = 0;
329
            }
330
            $plid = _l10n_update_locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $group, $comments, $mode, L10N_UPDATE_STRING_DEFAULT, $plid, $key);
331
          }
332
        }
333

    
334
        else {
335
          // A simple string to import.
336
          $english = $value['msgid'];
337
          $translation = $value['msgstr'];
338
          _l10n_update_locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode);
339
        }
340
      }
341
  } // end of db-store operation
342
}
343

    
344
/**
345
 * Import one string into the database.
346
 *
347
 * @param $report
348
 *   Report array summarizing the number of changes done in the form:
349
 *   array(inserts, updates, deletes).
350
 * @param $langcode
351
 *   Language code to import string into.
352
 * @param $context
353
 *   The context of this string.
354
 * @param $source
355
 *   Source string.
356
 * @param $translation
357
 *   Translation to language specified in $langcode.
358
 * @param $textgroup
359
 *   Name of textgroup to store translation in.
360
 * @param $location
361
 *   Location value to save with source string.
362
 * @param $mode
363
 *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
364
 * @param $status
365
 *   Status of translation if created: L10N_UPDATE_STRING_DEFAULT or L10N_UPDATE_STRING_CUSTOM
366
 * @param $plid
367
 *   Optional plural ID to use.
368
 * @param $plural
369
 *   Optional plural value to use.
370
 * @return
371
 *   The string ID of the existing string modified or the new string added.
372
 */
373
function _l10n_update_locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $textgroup, $location, $mode, $status = L10N_UPDATE_STRING_DEFAULT, $plid = 0, $plural = 0) {
374
  $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField();
375

    
376
  if (!empty($translation)) {
377
    // Skip this string unless it passes a check for dangerous code.
378
    // Text groups other than default still can contain HTML tags
379
    // (i.e. translatable blocks).
380
    if ($textgroup == "default" && !locale_string_is_safe($translation)) {
381
      $report['skips']++;
382
      $lid = 0;
383
      watchdog('locale', 'Disallowed HTML detected. String not imported: %string', array('%string' => $translation), WATCHDOG_WARNING);
384
    }
385
    elseif ($lid) {
386
      // We have this source string saved already.
387
      db_update('locales_source')
388
        ->fields(array(
389
          'location' => $location,
390
        ))
391
        ->condition('lid', $lid)
392
        ->execute();
393

    
394
      $exists = db_query("SELECT lid, l10n_status FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchObject();
395

    
396
      if (!$exists) {
397
        // No translation in this language.
398
        db_insert('locales_target')
399
          ->fields(array(
400
            'lid' => $lid,
401
            'language' => $langcode,
402
            'translation' => $translation,
403
            'plid' => $plid,
404
            'plural' => $plural,
405
          ))
406
          ->execute();
407

    
408
        $report['additions']++;
409
      }
410
      elseif (($exists->l10n_status == L10N_UPDATE_STRING_DEFAULT && $mode == LOCALE_UPDATE_OVERRIDE_DEFAULT) || $mode == LOCALE_IMPORT_OVERWRITE) {
411
        // Translation exists, only overwrite if instructed.
412
        db_update('locales_target')
413
          ->fields(array(
414
            'translation' => $translation,
415
            'plid' => $plid,
416
            'plural' => $plural,
417
          ))
418
          ->condition('language', $langcode)
419
          ->condition('lid', $lid)
420
          ->execute();
421

    
422
        $report['updates']++;
423
      }
424
    }
425
    else {
426
      // No such source string in the database yet.
427
      $lid = db_insert('locales_source')
428
        ->fields(array(
429
          'location' => $location,
430
          'source' => $source,
431
          'context' => (string) $context,
432
          'textgroup' => $textgroup,
433
        ))
434
        ->execute();
435

    
436
      db_insert('locales_target')
437
        ->fields(array(
438
           'lid' => $lid,
439
           'language' => $langcode,
440
           'translation' => $translation,
441
           'plid' => $plid,
442
           'plural' => $plural,
443
           'l10n_status' => $status,
444
        ))
445
        ->execute();
446

    
447
      $report['additions']++;
448
    }
449
  }
450
  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
451
    // Empty translation, remove existing if instructed.
452
    db_delete('locales_target')
453
      ->condition('language', $langcode)
454
      ->condition('lid', $lid)
455
      ->condition('plid', $plid)
456
      ->condition('plural', $plural)
457
      ->execute();
458

    
459
    $report['deletes']++;
460
  }
461

    
462
  return $lid;
463
}