From 5824698b1ffc7b2cd4bea1be0d1f63a1c1132ac7 Mon Sep 17 00:00:00 2001 From: Florian Weber Date: Thu, 14 Jun 2012 15:49:21 +0200 Subject: [PATCH] Issue #1635084: Track import status of files in the locale .po file directory. --- .../Drupal/locale/Tests/LocaleFileImportStatus.php | 180 ++++++++++++++++++++ core/modules/locale/locale.bulk.inc | 105 +++++++++++- core/modules/locale/locale.install | 72 ++++++++ core/modules/locale/locale.module | 4 + core/modules/locale/tests/test.de.po | 28 +++ 5 files changed, 384 insertions(+), 5 deletions(-) create mode 100644 core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php create mode 100644 core/modules/locale/tests/test.de.po diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php new file mode 100644 index 0000000..a81b88e --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php @@ -0,0 +1,180 @@ + 'Translation file import status', + 'description' => 'Tests the status of imported translation files.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Set the translation file directory. + variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests'); + } + + /** + * Add a language. + * + * @param $langcode + * The language of the langcode to add. + */ + function addLanguage($langcode) { + // Add language. + $edit = array('predefined_langcode' => $langcode); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + drupal_static_reset('langauge_list'); + $this->assertTrue(language_load($langcode), t('Language %langcode added.', array('%langcode' => $langcode))); + } + + /** + * Get translations for a array of strings. + * + * @param $strings + * A array of strings to translate. + * @param $langcode + * The language code of the language to translate to. + */ + function checkTranslations($strings, $langcode) { + foreach ($strings as $source => $translation) { + $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField(); + $this->assertEqual((string) $translation, (string) $db_translation); + } + } + + /** + * Import a single interface translation file. + * + * @param $langcode + * Langcode of the po file and language to import. + * @param int $timestamp_difference + * (optional) Timestamp offset, used to mock older or newer files. + * + * @return \Drupal\entity\EntityInterface + */ + function mockImportedPoFile($langcode, $timestamp_difference = 0) { + $dir = variable_get('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests'); + $testfile_uri = $dir . '/test.' . $langcode . '.po'; + + $file = locale_translate_file_entity_create($testfile_uri); + $file->original_timestamp = $file->timestamp; + $file->timestamp = $file->timestamp + $timestamp_difference; + $file->langcode = $langcode; + + // Fill the {locale_file} with a custom timestamp. + if ($timestamp_difference != 0) { + locale_translate_update_file_history($file); + } + + $count = db_query('SELECT COUNT(*) FROM {locale_file} WHERE langcode = :langcode', array(':langcode' => $langcode))->fetchField(); + $this->assertEqual(1, $count, t('%count file registered in {locale_file}.', array('%count' => $count))); + + $result = db_query('SELECT langcode, uri FROM {locale_file}')->fetchAssoc(); + $this->assertEqual($result['uri'], $testfile_uri, t('%uri is in {locale_file}.', array('%uri' => $result['uri']))); + $this->assertEqual($result['langcode'], $langcode, t('Langcode is %langcode.', array('%langcode' => $langcode))); + return $file; + } + + /** + * Test the basic bulk import functionality. + */ + function testBulkImport() { + $langcode = 'de'; + + // Translations should not exist. + $strings = array( + 'Monday' => '', + 'Tuesday' => '', + ); + $this->checkTranslations($strings, $langcode); + + // Add language. + $this->addLanguage($langcode); + + // The file was imported, translations should exist. + $strings = array( + 'Monday' => 'Montag', + 'Tuesday' => 'Dienstag', + ); + $this->checkTranslations($strings, $langcode); + } + + /** + * Update a pre-existing file. + */ + function testBulkImportUpdateExisting() { + $langcode = 'de'; + + // Translations should not exist. + $strings = array( + 'Monday' => '', + 'Tuesday' => '', + ); + $this->checkTranslations($strings, $langcode); + + // Fill the {locale_file} table with a older file. + $file = $this->mockImportedPoFile($langcode, -1); + + // Add language. + $this->addLanguage($langcode); + + // The file was imported, translations should exist. + $strings = array( + 'Monday' => 'Montag', + 'Tuesday' => 'Dienstag', + ); + $this->checkTranslations($strings, $langcode); + + $timestamp = db_query('SELECT timestamp FROM {locale_file} WHERE uri = :uri', array(':uri' => $file->uri))->fetchField(); + $this->assertEqual($timestamp, $file->original_timestamp, t('File is updated.')); + } + + /** + * Don't update a pre-existing file. + */ + function testBulkImportNotUpdateExisting() { + $langcode = 'de'; + + // Translations should not exist. + $strings = array( + 'Monday' => '', + 'Tuesday' => '', + ); + $this->checkTranslations($strings, $langcode); + + // Fill the {locale_file} table with a newer file. + $file = $this->mockImportedPoFile($langcode, 1); + + // Add language. + $this->addLanguage($langcode); + + // The file was not imported, the translation should not exist. + $strings = array( + 'Monday' => '', + 'Tuesday' => '', + ); + $this->checkTranslations($strings, $langcode); + + $timestamp = db_query('SELECT timestamp FROM {locale_file} WHERE uri = :uri', array(':uri' => $file->uri))->fetchField(); + $this->assertEqual($timestamp, $file->timestamp); + } +} diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 7a8a977..0f04056 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -228,14 +228,15 @@ function locale_translate_add_language_set_batch($langcode) { * (optional) Language code to limit files being imported. * @param $finish_feedback * (optional) Whether to give feedback to the user when finished. + * @param $force + * (optional) Import all available files, even if they were imported before. * * @todo * Integrate with update status to identify projects needed and integrate * l10n_update functionality to feed in translation files alike. * See http://drupal.org/node/1191488. */ -function locale_translate_batch_import_files($langcode = NULL, $finish_feedback = FALSE) { - $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations'); +function locale_translate_batch_import_files($langcode = NULL, $finish_feedback = FALSE, $force = FALSE) { $files = array(); if (!empty($langcode)) { $langcodes = array($langcode); @@ -246,12 +247,41 @@ function locale_translate_batch_import_files($langcode = NULL, $finish_feedback $langcodes = array_keys(language_list()); } foreach ($langcodes as $langcode) { - $files = array_merge($files, file_scan_directory($directory, '!' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '') . '\.po$!', array('recurse' => FALSE))); + $files = array_merge($files, locale_translate_get_interface_translation_files($langcode)); + } + if (!$force) { + $result = db_select('locale_file', 'lf') + ->fields('lf', array('langcode', 'uri', 'timestamp')) + ->condition('langcode', $langcodes) + ->execute() + ->fetchAllAssoc('uri'); + foreach ($result as $uri => $info) { + if (isset($files[$uri]) && filemtime($uri) <= $info->timestamp) { + // The file is already imported and it did not change since the import. + // Remove it from file list and don't it again. + unset($files[$uri]); + } + } } return locale_translate_batch_build($files, $finish_feedback); } /** + * Get a array of available interface translation file. + * + * @param $langcode + * The langcode for the interface translation files. Pass NULL to get all + * available interface translation files. + * + * @return array + * A array of interface translation files. + */ +function locale_translate_get_interface_translation_files($langcode = NULL) { + $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations'); + return file_scan_directory($directory, '!' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '') . '\.po$!', array('recurse' => FALSE)); +} + +/** * Build a locale batch from an array of files. * * @param $files @@ -297,8 +327,12 @@ function locale_translate_batch_import($filepath, &$context) { // The filename is either {langcode}.po or {prefix}.{langcode}.po, so // we can extract the language code to use for the import from the end. if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) { - $file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath)); - _locale_import_read_po('db-store', $file, array(), $langcode[2]); + $file = locale_translate_file_entity_create($filepath, $langcode[2]); + $success = _locale_import_read_po('db-store', $file, array(), $langcode[2]); + if ($success == NULL) { + $file->langcode = $langcode[2]; + locale_translate_update_file_history($file); + } $context['results'][] = $filepath; } } @@ -311,3 +345,64 @@ function locale_translate_batch_finished($success, $results) { drupal_set_message(format_plural(count($results), 'One translation file imported.', '@count translation files imported.')); } } + +/** + * Wrapper for entity_create(). Populates the timestamp property. + * + * @param $filepath + * The filepath of a file to import. + * + * @return Drupal\entity\EntityInterface + * + * @see entity_create() + */ +function locale_translate_file_entity_create($filepath) { + $file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath)); + $file->timestamp = filemtime($file->uri); + return $file; +} + +/** + * Update the {locale_file} table. + * + * @param $file + * Object representing the file just imported. + * + * @return integer + * FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED. + * + * @see drupal_write_record() + */ +function locale_translate_update_file_history($file) { + // Update or write new record. + if (db_query("SELECT uri FROM {locale_file} WHERE uri = :uri AND langcode = :langcode", array(':uri' => $file->uri, ':langcode' => $file->langcode))->fetchField()) { + $update = array('uri', 'langcode'); + } + else { + $update = array(); + } + return drupal_write_record('locale_file', $file, $update); +} + +/** + * Deletes all interface translation files depending on the langcode. + * + * @param $langcode + * A langcode or NULL. Pass NULL to delete all interface translation files. + */ +function locale_translate_delete_translation_files($langcode) { + $files = locale_translate_get_interface_translation_files($langcode); + if (!empty($files)) { + foreach ($files as $file) { + $success = file_unmanaged_delete($files->uri); + if (!$success) { + return FALSE; + } + } + } + // Remove registered translation files. + db_delete('locale_file') + ->condition('langcode', $langcode) + ->execute(); + return TRUE; +} diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index ac03baa..a7885fb 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -127,6 +127,39 @@ function locale_schema() { ), ); + $schema['locale_file'] = array( + 'description' => 'File import status information for interface translation files.', + 'fields' => array( + 'langcode' => array( + 'description' => 'Reference to the {languages}.langcode for this translation.', + 'type' => 'varchar', + 'length' => '12', + 'not null' => TRUE, + ), + 'filename' => array( + 'description' => 'Filename for importing the file.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'uri' => array( + 'description' => 'File system path for importing the file.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'timestamp' => array( + 'description' => 'Unix timestamp of the file itself from the point when it was last imported.', + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + ), + ), + 'primary key' => array('uri', 'langcode'), + ); + return $schema; } @@ -552,6 +585,45 @@ function locale_update_8009() { } /** + * Add {locale_file} table. + */ +function locale_update_8010() { + $table = array( + 'description' => 'File import status information for interface translation files.', + 'fields' => array( + 'langcode' => array( + 'description' => 'Reference to the {languages}.langcode for this translation.', + 'type' => 'varchar', + 'length' => '12', + 'not null' => TRUE, + ), + 'filename' => array( + 'description' => 'Filename for importing the file.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'uri' => array( + 'description' => 'File system path for importing the file.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'timestamp' => array( + 'description' => 'Unix timestamp of the file itself from the point when it was last imported.', + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + ), + ), + 'primary key' => array('uri', 'langcode'), + ); + db_create_table('locale_file', $table); +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index ae4c5f0..1946cf0 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -363,6 +363,10 @@ function locale_language_delete($language) { ->condition('language', $language->langcode) ->execute(); + // Remove interface translation files. + module_load_include('inc', 'locale', 'locale.bulk'); + locale_translate_delete_translation_files($language->langcode); + _locale_invalidate_js($language->langcode); // Changing the language settings impacts the interface: diff --git a/core/modules/locale/tests/test.de.po b/core/modules/locale/tests/test.de.po new file mode 100644 index 0000000..85e2841 --- /dev/null +++ b/core/modules/locale/tests/test.de.po @@ -0,0 +1,28 @@ +msgid "" +msgstr "" +"Project-Id-Version: Drupal 7\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Monday" +msgstr "Montag" + +msgid "Tuesday" +msgstr "Dienstag" + +msgid "Wednesday" +msgstr "Mittwoch" + +msgid "Thursday" +msgstr "Donnerstag" + +msgid "Friday" +msgstr "Freitag" + +msgid "Saturday" +msgstr "Samstag" + +msgid "Sunday" +msgstr "Sonntag" -- 1.7.10.3