123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- <?php
- /**
- * @file
- * Override part of locale.inc library so we can manage string status
- */
- /**
- * Parses Gettext Portable Object file information and inserts into database
- *
- * This is an improved version of _locale_import_po() to handle translation status
- *
- * @param $file
- * Drupal file object corresponding to the PO file to import
- * @param $langcode
- * Language code
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
- * @param $group
- * Text group to import PO file into (eg. 'default' for interface translations)
- *
- * @return boolean
- * Result array on success. FALSE on failure
- */
- function _l10n_update_locale_import_po($file, $langcode, $mode, $group = NULL) {
- // Try to allocate enough time to parse and import the data.
- drupal_set_time_limit(240);
- // Check if we have the language already in the database.
- if (!db_query("SELECT COUNT(language) FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchField()) {
- drupal_set_message(t('The language selected for import is not supported.'), 'error');
- return FALSE;
- }
- // Get strings from file (returns on failure after a partial import, or on success)
- $status = _l10n_update_locale_import_read_po('db-store', $file, $mode, $langcode, $group);
- if ($status === FALSE) {
- // Error messages are set in _locale_import_read_po().
- return FALSE;
- }
- // Get status information on import process.
- list($header_done, $additions, $updates, $deletes, $skips) = _l10n_update_locale_import_one_string('db-report');
- if (!$header_done) {
- drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
- }
- // Clear cache and force refresh of JavaScript translations.
- _locale_invalidate_js($langcode);
- cache_clear_all('locale:', 'cache', TRUE);
- // Rebuild the menu, strings may have changed.
- menu_rebuild();
- 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));
- if ($skips) {
- watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
- }
- // Return results of this import.
- return array(
- 'file' => $file,
- 'language' => $langcode,
- 'add' => $additions,
- 'update' => $updates,
- 'delete' => $deletes,
- 'skip' => $skips,
- );
- }
- /**
- * Parses Gettext Portable Object file into an array
- *
- * @param $op
- * Storage operation type: db-store or mem-store
- * @param $file
- * Drupal file object corresponding to the PO file to import
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
- * @param $lang
- * Language code
- * @param $group
- * Text group to import PO file into (eg. 'default' for interface translations)
- */
- function _l10n_update_locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
- $fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
- if (!$fd) {
- _locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
- return FALSE;
- }
- $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
- $current = array(); // Current entry being read
- $plural = 0; // Current plural form
- $lineno = 0; // Current line
- while (!feof($fd)) {
- $line = fgets($fd, 10*1024); // A line should not be this long
- if ($lineno == 0) {
- // The first line might come with a UTF-8 BOM, which should be removed.
- $line = str_replace("\xEF\xBB\xBF", '', $line);
- }
- $lineno++;
- $line = trim(strtr($line, array("\\\n" => "")));
- if (!strncmp("#", $line, 1)) { // A comment
- if ($context == "COMMENT") { // Already in comment context: add
- $current["#"][] = substr($line, 1);
- }
- elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
- _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
- $current = array();
- $current["#"][] = substr($line, 1);
- $context = "COMMENT";
- }
- else { // Parse error
- _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
- return FALSE;
- }
- }
- elseif (!strncmp("msgid_plural", $line, 12)) {
- if ($context != "MSGID") { // Must be plural form for current entry
- _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
- return FALSE;
- }
- $line = trim(substr($line, 12));
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $current["msgid"] = $current["msgid"] . "\0" . $quoted;
- $context = "MSGID_PLURAL";
- }
- elseif (!strncmp("msgid", $line, 5)) {
- if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
- _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
- $current = array();
- }
- elseif ($context == "MSGID") { // Already in this context? Parse error
- _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno);
- return FALSE;
- }
- $line = trim(substr($line, 5));
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $current["msgid"] = $quoted;
- $context = "MSGID";
- }
- elseif (!strncmp("msgctxt", $line, 7)) {
- if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
- _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
- $current = array();
- }
- elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
- _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno);
- return FALSE;
- }
- $line = trim(substr($line, 7));
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $current["msgctxt"] = $quoted;
- $context = "MSGCTXT";
- }
- elseif (!strncmp("msgstr[", $line, 7)) {
- if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
- _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
- return FALSE;
- }
- if (strpos($line, "]") === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $frombracket = strstr($line, "[");
- $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
- $line = trim(strstr($line, " "));
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $current["msgstr"][$plural] = $quoted;
- $context = "MSGSTR_ARR";
- }
- elseif (!strncmp("msgstr", $line, 6)) {
- if (($context != "MSGID") && ($context != "MSGCTXT")) { // Should come just after a msgid or msgctxt block
- _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno);
- return FALSE;
- }
- $line = trim(substr($line, 6));
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- $current["msgstr"] = $quoted;
- $context = "MSGSTR";
- }
- elseif ($line != "") {
- $quoted = _locale_import_parse_quoted($line);
- if ($quoted === FALSE) {
- _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
- return FALSE;
- }
- if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
- $current["msgid"] .= $quoted;
- }
- elseif ($context == "MSGCTXT") {
- $current["msgctxt"] .= $quoted;
- }
- elseif ($context == "MSGSTR") {
- $current["msgstr"] .= $quoted;
- }
- elseif ($context == "MSGSTR_ARR") {
- $current["msgstr"][$plural] .= $quoted;
- }
- else {
- _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno);
- return FALSE;
- }
- }
- }
- // End of PO file, flush last entry.
- if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
- _l10n_update_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
- }
- elseif ($context != "COMMENT") {
- _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
- return FALSE;
- }
- }
- /**
- * Imports a string into the database
- *
- * @param $op
- * Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'
- * @param $value
- * Details of the string stored
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
- * @param $lang
- * Language to store the string in
- * @param $file
- * Object representation of file being imported, only required when op is 'db-store'
- * @param $group
- * Text group to import PO file into (eg. 'default' for interface translations)
- */
- function _l10n_update_locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') {
- $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
- $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
- $strings = &drupal_static(__FUNCTION__ . ':strings', array());
- switch ($op) {
- // Return stored strings
- case 'mem-report':
- return $strings;
- // Store string in memory (only supports single strings)
- case 'mem-store':
- $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr'];
- return;
- // Called at end of import to inform the user
- case 'db-report':
- return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']);
- // Store the string we got in the database.
- case 'db-store':
- // We got header information.
- if ($value['msgid'] == '') {
- $languages = language_list();
- if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) {
- // Since we only need to parse the header if we ought to update the
- // plural formula, only run this if we don't need to keep existing
- // data untouched or if we don't have an existing plural formula.
- $header = _locale_import_parse_header($value['msgstr']);
- // Get the plural formula and update in database.
- if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) {
- list($nplurals, $plural) = $p;
- db_update('languages')
- ->fields(array(
- 'plurals' => $nplurals,
- 'formula' => $plural,
- ))
- ->condition('language', $lang)
- ->execute();
- }
- else {
- db_update('languages')
- ->fields(array(
- 'plurals' => 0,
- 'formula' => '',
- ))
- ->condition('language', $lang)
- ->execute();
- }
- }
- $header_done = TRUE;
- }
- else {
- // Some real string to import.
- $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
- if (strpos($value['msgid'], "\0")) {
- // This string has plural versions.
- $english = explode("\0", $value['msgid'], 2);
- $entries = array_keys($value['msgstr']);
- for ($i = 3; $i <= count($entries); $i++) {
- $english[] = $english[1];
- }
- $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries);
- $english = array_map('_locale_import_append_plural', $english, $entries);
- foreach ($translation as $key => $trans) {
- if ($key == 0) {
- $plid = 0;
- }
- $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);
- }
- }
- else {
- // A simple string to import.
- $english = $value['msgid'];
- $translation = $value['msgstr'];
- _l10n_update_locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode);
- }
- }
- } // end of db-store operation
- }
- /**
- * Import one string into the database.
- *
- * @param $report
- * Report array summarizing the number of changes done in the form:
- * array(inserts, updates, deletes).
- * @param $langcode
- * Language code to import string into.
- * @param $context
- * The context of this string.
- * @param $source
- * Source string.
- * @param $translation
- * Translation to language specified in $langcode.
- * @param $textgroup
- * Name of textgroup to store translation in.
- * @param $location
- * Location value to save with source string.
- * @param $mode
- * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
- * @param $status
- * Status of translation if created: L10N_UPDATE_STRING_DEFAULT or L10N_UPDATE_STRING_CUSTOM
- * @param $plid
- * Optional plural ID to use.
- * @param $plural
- * Optional plural value to use.
- * @return
- * The string ID of the existing string modified or the new string added.
- */
- 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) {
- $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();
- if (!empty($translation)) {
- // Skip this string unless it passes a check for dangerous code.
- // Text groups other than default still can contain HTML tags
- // (i.e. translatable blocks).
- if ($textgroup == "default" && !locale_string_is_safe($translation)) {
- $report['skips']++;
- $lid = 0;
- watchdog('locale', 'Disallowed HTML detected. String not imported: %string', array('%string' => $translation), WATCHDOG_WARNING);
- }
- elseif ($lid) {
- // We have this source string saved already.
- db_update('locales_source')
- ->fields(array(
- 'location' => $location,
- ))
- ->condition('lid', $lid)
- ->execute();
- $exists = db_query("SELECT lid, l10n_status FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchObject();
- if (!$exists) {
- // No translation in this language.
- db_insert('locales_target')
- ->fields(array(
- 'lid' => $lid,
- 'language' => $langcode,
- 'translation' => $translation,
- 'plid' => $plid,
- 'plural' => $plural,
- ))
- ->execute();
- $report['additions']++;
- }
- elseif (($exists->l10n_status == L10N_UPDATE_STRING_DEFAULT && $mode == LOCALE_UPDATE_OVERRIDE_DEFAULT) || $mode == LOCALE_IMPORT_OVERWRITE) {
- // Translation exists, only overwrite if instructed.
- db_update('locales_target')
- ->fields(array(
- 'translation' => $translation,
- 'plid' => $plid,
- 'plural' => $plural,
- ))
- ->condition('language', $langcode)
- ->condition('lid', $lid)
- ->execute();
- $report['updates']++;
- }
- }
- else {
- // No such source string in the database yet.
- $lid = db_insert('locales_source')
- ->fields(array(
- 'location' => $location,
- 'source' => $source,
- 'context' => (string) $context,
- 'textgroup' => $textgroup,
- ))
- ->execute();
- db_insert('locales_target')
- ->fields(array(
- 'lid' => $lid,
- 'language' => $langcode,
- 'translation' => $translation,
- 'plid' => $plid,
- 'plural' => $plural,
- 'l10n_status' => $status,
- ))
- ->execute();
- $report['additions']++;
- }
- }
- elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
- // Empty translation, remove existing if instructed.
- db_delete('locales_target')
- ->condition('language', $langcode)
- ->condition('lid', $lid)
- ->condition('plid', $plid)
- ->condition('plural', $plural)
- ->execute();
- $report['deletes']++;
- }
- return $lid;
- }
|