Projet

Général

Profil

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

root / drupal7 / sites / all / modules / l10n_update / l10n_update.locale.inc @ 503b3f7b

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
  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));
50
  if ($skips) {
51
    watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
52
  }
53

    
54
  // Return results of this import.
55
  return array(
56
    'file' => $file,
57
    'language' => $langcode,
58
    'add' => $additions,
59
    'update' => $updates,
60
    'delete' => $deletes,
61
    'skip' => $skips,
62
  );
63
}
64

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

    
81
  $fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
82
  if (!$fd) {
83
    _locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
84
    return FALSE;
85
  }
86

    
87
  $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
88
  $current = array();   // Current entry being read
89
  $plural = 0;          // Current plural form
90
  $lineno = 0;          // Current line
91

    
92
  while (!feof($fd)) {
93
    $line = fgets($fd, 10*1024); // A line should not be this long
94
    if ($lineno == 0) {
95
      // The first line might come with a UTF-8 BOM, which should be removed.
96
      $line = str_replace("\xEF\xBB\xBF", '', $line);
97
    }
98
    $lineno++;
99
    $line = trim(strtr($line, array("\\\n" => "")));
100

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

    
225
  // End of PO file, flush last entry.
226
  if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
227
    _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
228
  }
229
  elseif ($context != "COMMENT") {
230
    _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
231
    return FALSE;
232
  }
233

    
234
}
235

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

    
257
  switch ($op) {
258
    // Return stored strings
259
    case 'mem-report':
260
      return $strings;
261

    
262
    // Store string in memory (only supports single strings)
263
    case 'mem-store':
264
      $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr'];
265
      return;
266

    
267
    // Called at end of import to inform the user
268
    case 'db-report':
269
      return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']);
270

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

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

    
306
      else {
307
        // Some real string to import.
308
        $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
309

    
310
        if (strpos($value['msgid'], "\0")) {
311
          // This string has plural versions.
312
          $english = explode("\0", $value['msgid'], 2);
313
          $entries = array_keys($value['msgstr']);
314
          for ($i = 3; $i <= count($entries); $i++) {
315
            $english[] = $english[1];
316
          }
317
          $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
318
          $english = array_map('_locale_import_append_plural', $english, $entries);
319
          foreach ($translation as $key => $trans) {
320
            if ($key == 0) {
321
              $plid = 0;
322
            }
323
            $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);
324
          }
325
        }
326

    
327
        else {
328
          // A simple string to import.
329
          $english = $value['msgid'];
330
          $translation = $value['msgstr'];
331
          _l10n_update_locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode);
332
        }
333
      }
334
  } // end of db-store operation
335
}
336

    
337
/**
338
 * Import one string into the database.
339
 *
340
 * @param $report
341
 *   Report array summarizing the number of changes done in the form:
342
 *   array(inserts, updates, deletes).
343
 * @param $langcode
344
 *   Language code to import string into.
345
 * @param $context
346
 *   The context of this string.
347
 * @param $source
348
 *   Source string.
349
 * @param $translation
350
 *   Translation to language specified in $langcode.
351
 * @param $textgroup
352
 *   Name of textgroup to store translation in.
353
 * @param $location
354
 *   Location value to save with source string.
355
 * @param $mode
356
 *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
357
 * @param $status
358
 *   Status of translation if created: L10N_UPDATE_STRING_DEFAULT or L10N_UPDATE_STRING_CUSTOM
359
 * @param $plid
360
 *   Optional plural ID to use.
361
 * @param $plural
362
 *   Optional plural value to use.
363
 * @return
364
 *   The string ID of the existing string modified or the new string added.
365
 */
366
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) {
367
  $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();
368

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

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

    
389
      if (!$exists) {
390
        // No translation in this language.
391
        db_insert('locales_target')
392
          ->fields(array(
393
            'lid' => $lid,
394
            'language' => $langcode,
395
            'translation' => $translation,
396
            'plid' => $plid,
397
            'plural' => $plural,
398
          ))
399
          ->execute();
400

    
401
        $report['additions']++;
402
      }
403
      elseif (($exists->l10n_status == L10N_UPDATE_STRING_DEFAULT && $mode == LOCALE_UPDATE_OVERRIDE_DEFAULT) || $mode == LOCALE_IMPORT_OVERWRITE) {
404
        // Translation exists, only overwrite if instructed.
405
        db_update('locales_target')
406
          ->fields(array(
407
            'translation' => $translation,
408
            'plid' => $plid,
409
            'plural' => $plural,
410
          ))
411
          ->condition('language', $langcode)
412
          ->condition('lid', $lid)
413
          ->execute();
414

    
415
        $report['updates']++;
416
      }
417
    }
418
    else {
419
      // No such source string in the database yet.
420
      $lid = db_insert('locales_source')
421
        ->fields(array(
422
          'location' => $location,
423
          'source' => $source,
424
          'context' => (string) $context,
425
          'textgroup' => $textgroup,
426
        ))
427
        ->execute();
428

    
429
      db_insert('locales_target')
430
        ->fields(array(
431
           'lid' => $lid,
432
           'language' => $langcode,
433
           'translation' => $translation,
434
           'plid' => $plid,
435
           'plural' => $plural,
436
           'l10n_status' => $status,
437
        ))
438
        ->execute();
439

    
440
      $report['additions']++;
441
    }
442
  }
443
  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
444
    // Empty translation, remove existing if instructed.
445
    db_delete('locales_target')
446
      ->condition('language', $langcode)
447
      ->condition('lid', $lid)
448
      ->condition('plid', $plid)
449
      ->condition('plural', $plural)
450
      ->execute();
451

    
452
    $report['deletes']++;
453
  }
454

    
455
  return $lid;
456
}