diff --git a/core/lib/Drupal/Component/Gettext/PoStreamReader.php b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
index 74a9bae..83ad207 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamReader.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
@@ -170,7 +170,7 @@ public function open() {
$this->readHeader();
}
else {
- throw new Exception('Cannot open stream without URI set.');
+ throw new \Exception('Cannot open stream without URI set.');
}
}
@@ -185,7 +185,7 @@ public function close() {
fclose($this->_fd);
}
else {
- throw new Exception('Cannot close stream that is not open.');
+ throw new \Exception('Cannot close stream that is not open.');
}
}
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
index 190d8ae..f2660fd 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
@@ -12,7 +12,6 @@
use Drupal\Component\Gettext\PoReaderInterface;
use Drupal\locale\TranslationString;
use PDO;
-use Exception;
/**
* Gettext PO reader working with the locale module database.
@@ -103,7 +102,7 @@ function getHeader() {
* Always, because you cannot set the PO header of a reader.
*/
function setHeader(PoHeader $header) {
- throw new Exception('You cannot set the PO header in a reader.');
+ throw new \Exception('You cannot set the PO header in a reader.');
}
/**
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
index f5b9934..337bd90 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
@@ -13,7 +13,6 @@
use Drupal\Component\Gettext\PoWriterInterface;
use Drupal\locale\SourceString;
use Drupal\locale\TranslationString;
-use Exception;
/**
* Gettext PO writer working with the locale module database.
@@ -161,14 +160,14 @@ function setHeader(PoHeader $header) {
// Check for options.
$options = $this->getOptions();
if (empty($options)) {
- throw new Exception("Options should be set before assigning a PoHeader.");
+ throw new \Exception("Options should be set before assigning a PoHeader.");
}
$overwrite_options = $options['overwrite_options'];
// Check for langcode.
$langcode = $this->_langcode;
if (empty($langcode)) {
- throw new Exception("Langcode should be set before assigning a PoHeader.");
+ throw new \Exception("Langcode should be set before assigning a PoHeader.");
}
if (array_sum($overwrite_options) || empty($locale_plurals[$langcode]['plurals'])) {
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
index 06d292f..5d7ce3a 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
@@ -89,7 +89,7 @@ function mockImportedPoFile($langcode, $timestamp_difference = 0) {
// Fill the {locale_file} with a custom timestamp.
if ($timestamp_difference != 0) {
- locale_translate_update_file_history($file);
+ locale_translate_updation_file_history($file);
}
$count = db_query('SELECT COUNT(*) FROM {locale_file} WHERE langcode = :langcode', array(':langcode' => $langcode))->fetchField();
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index 215cb75..c4b6acd 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -6,42 +6,6 @@
*/
/**
- * Build a batch to get the status of remote and local translation files.
- *
- * The batch process fetches the state of both remote and (if configured) local
- * translation files. The data of the most recent translation is stored per
- * per project and per language. This data is stored in a state variable
- * 'locale_translation_status'. The timestamp it was last updated is stored
- * in the state variable 'locale_translation_status_last_update'.
- *
- * @param array $sources
- * Array of translation source objects for which to check the state of
- * translation source files.
- */
-function locale_translation_batch_status_build($sources) {
- $operations = array();
-
- // Set the batch processes for remote sources.
- foreach ($sources as $source) {
- $operations[] = array('locale_translation_batch_status_fetch_remote', array($source));
- }
-
- // Check for local sources, compare the results of local and remote and store
- // the most recent.
- $operations[] = array('locale_translation_batch_status_fetch_local', array($sources));
- $operations[] = array('locale_translation_batch_status_compare', array());
-
- $batch = array(
- 'operations' => $operations,
- 'title' => t('Checking available translations'),
- 'finished' => 'locale_translation_batch_status_finished',
- 'error_message' => t('Error checking available interface translation updates.'),
- 'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
- );
- return $batch;
-}
-
-/**
* Batch operation callback: Check the availability of a remote po file.
*
* Checks the presence and creation time of one po file per batch process. The
@@ -62,12 +26,12 @@ function locale_translation_batch_status_fetch_remote($source, &$context) {
// data with the remote status.
if (isset($source->files['remote'])) {
$remote_file = $source->files['remote'];
- $result = locale_translation_http_check($remote_file->url);
+ $result = locale_translation_http_check($remote_file->uri);
// Update the file object with the result data. In case of a redirect we
- // store the resulting url.
+ // store the resulting uri.
if ($result && !empty($result->updated)) {
- $remote_file->url = isset($result->redirect_url) ? $result->redirect_url : $remote_file->url;
+ $remote_file->uri = isset($result->redirect_uri) ? $result->redirect_uri : $remote_file->uri;
$remote_file->timestamp = $result->updated;
$source->files['remote'] = $remote_file;
}
@@ -128,6 +92,7 @@ function locale_translation_batch_status_fetch_local($sources, &$context) {
*/
function locale_translation_batch_status_compare(&$context) {
module_load_include('compare.inc', 'locale');
+ $history = locale_translation_get_file_history();
$results = array();
foreach ($context['results']['sources'] as $project => $langcodes) {
@@ -141,8 +106,32 @@ function locale_translation_batch_status_compare(&$context) {
if (isset($file->timestamp)) {
$source->type = $file->type;
$source->timestamp = $file->timestamp;
- $results[$project][$langcode] = $source;
}
+
+ // Compare the available translation with the current translations status.
+ // If the project/language was translated before and it is more recent
+ // than the most recent translation, the translation is up to date. Which
+ // is marked in the source object with type "current".
+ if (isset($history[$source->project][$source->language])) {
+ $current = $history[$source->project][$source->language];
+ // Add the current translation to the source object to save it in
+ // the status cache.
+ $source->files['current'] = $current;
+
+ if (isset($source->type)) {
+ $available = $source->files[$source->type];
+ $result = _locale_translation_source_compare($current, $available) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $available : $current;
+ $source->type = $result->type;
+ $source->timestamp = $result->timestamp;
+ }
+ else {
+ $source->type = $current->type;
+ $source->timestamp = $current->timestamp;
+ }
+ }
+
+
+ $results[$project][$langcode] = $source;
}
}
@@ -160,9 +149,9 @@ function locale_translation_batch_status_compare(&$context) {
function locale_translation_batch_status_finished($success, $results) {
$t = get_t();
if($success) {
- if ($results) {
+ if ($results['sources']) {
drupal_set_message(format_plural(
- count($results),
+ count($results['sources']),
'Checked available interface translation updates for one project.',
'Checked available interface translation updates for @count projects.'
));
@@ -174,46 +163,6 @@ function locale_translation_batch_status_finished($success, $results) {
}
/**
- * Create a batch to just download files.
- *
- * @param $updates
- * Translations sources to be downloaded.
- * Note: All update sources must have a 'url'.
- * @return array
- * A batch definition for this download.
- */
-// @todo convert this function. Is this function required?
-function l10n_update_batch_download($updates) {
- foreach ($updates as $update) {
- $id = $update->filename;
- $operations[] = array('_l10n_update_batch_download', array($id, $update));
- }
- return _l10n_update_create_batch($operations);
-}
-
-/**
- * Create a batch to just import files.
- *
- * All update sources must have a 'uri'.
- *
- * @param $updates
- * Translations sources to be imported.
- * Note: All update sources must have a 'url'.
- * @param $import_mode
- * Import mode. How to treat existing and modified translations.
- * @return array
- * A batch definition for this import.
- */
-// @todo convert this function. Is this function required?
-function l10n_update_batch_import($updates, $import_mode) {
- foreach ($updates as $update) {
- $id = $update->filename;
- $operations[] = array('_l10n_update_batch_import', array($id, $update, $import_mode));
- }
- return _l10n_update_create_batch($operations);
-}
-
-/**
* Batch operation: Download a remote translation file.
*
* @param $translation source object
@@ -230,9 +179,9 @@ function locale_translation_batch_fetch_download($source, &$context) {
}
else {
// @todo Error handling when download fails.
- $source->files['remote']->error = 'TODO download error';
+ watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source->files['remote']->uri), WATCHDOG_ERROR);
+ $context['results']['failed_files'][] = $source->files['remote'];
}
-//dpm($source);
$context['results']['sources'][$source->name][$source->language] = $source;
}
@@ -255,26 +204,30 @@ function locale_translation_batch_fetch_download($source, &$context) {
*/
function locale_translation_batch_fetch_import($source, $options, &$context) {
$t = get_t();
- // Import a downloaded file.
+ // If we are working on a remote file we will import the downloaded file and
+ // use the result data from the
if ($source->type == 'remote') {
if (isset($context['results']['sources'][$source->project][$source->language]->files['download'])) {
- $file = $context['results']['sources'][$source->project][$source->language]->files['download'];
+ $import_type = 'download';
+ $source_type = 'remote';
+ $source_result = $context['results']['sources'][$source->project][$source->language];
}
}
- else {
- $file = $source->files['local'];
+ if ($source->type == 'local') {
+ $import_type = 'local';
+ $source_type = 'local';
+ $source_result = $source;
}
-//dpm($file);
+
// @todo Fault handling using try/catch. For other batch functions too?
- if (isset($file)) {
+ if (isset($import_type)) {
+ $file = $source_result->files[$import_type];
module_load_include('bulk.inc', 'locale');
$options += array(
'customized' => LOCALE_NOT_CUSTOMIZED,
'message' => 'Importing: ' . $file->filename,
);
- // @todo Not elegant, is there a better way for both local and remote imports?
- $file->langcode = $source->language;
locale_translate_batch_import($file, $options, $context);
// Import is finished.
@@ -283,53 +236,46 @@ function locale_translation_batch_fetch_import($source, $options, &$context) {
if (isset($context['results']['files'][$file->uri])) {
$context['message'] = $t('Imported: %name.', array('%name' => $file->filename));
- // @todo store data of imported file preparing the data to be saved in {locale_file}.
- $source->files['import'] = $file;
-
- // @todo process $context['stats']
+ // Store the data of imported source. This data can later be saved in {locale_file}.
+ $source_result->files['import'] = $source_result->files[$source_type];
// Downloaded files are stored in the temporary files directory. On
// succesfull import, the files are moved to the local destination
// directory if files should be kept locally. Otherwise the temporary
// files are deleted.
- if ($file->type == 'download') {
- if (variable_get('locale_translate_file_directory', conf_path() . '/files/translations') && isset($source->files['local'])) {
- // Move the temporary file to the local translations directory and
- // update the local file data.
- $destination = $file->directory . $file->filename;
- // @todo Does this work in the file can not be moved?
- if (file_unmanaged_move($file->uri, $destination, FILE_EXISTS_REPLACE)) {
- $timestamp = filemtime($destination);
- $source->files['local']->timestamp = $timestamp;
- $source->files['import']->timestamp = $timestamp;
- $source->files['import']->last_checked = $timestamp;
- $source->files['import']->uri = $source->files['local']->directory . $source->files['local']->filename;
- }
- else {
- // The temporary file could not be moved. Record the error and
- // delete the temporary file.
- // @todo Throw error? Watchdog or message?
- unset($source->files['local']->timestamp);
- $source->files['local']->error = 'TODO failure writing the file';
- file_unmanaged_delete($file->uri);
- }
- }
- else {
- // The temporary file is now imported and will not be used further.
- // It can be deleted.
- file_unmanaged_delete($file->uri);
+ if ($import_type == 'download' && variable_get('locale_translate_file_directory', conf_path() . '/files/translations') && isset($source_result->files['local'])) {
+ // Move the temporary file to the local translations directory.
+ // @todo What to do if the file can not be moved? If it throws an error (watchdog), should we set a batch error?
+ if (file_unmanaged_move($file->uri, $source_result->files['local']->uri, FILE_EXISTS_REPLACE)) {
+ // The downloaded file is now moved to the local file location.
+ // From this point forward we can treat it as if we imported a
+ // local file.
+ $import_type = 'local';
}
}
+ if ($import_type == 'download') {
+ // The temporary file is now imported and will not be used further.
+ // Store the timestamp and delete the file.
+ $timestamp = filemtime($source_result->files[$import_type]->uri);
+ $source_result->files['import']->timestamp = $timestamp;
+ $source_result->files['import']->last_checked = $timestamp;
+ file_unmanaged_delete($file->uri);
+ }
+ if ($import_type == 'local') {
+ // Import type is 'local'.
+ $timestamp = filemtime($source_result->files[$import_type]->uri);
+ $source_result->files['local']->timestamp = $timestamp;
+ $source_result->files['import']->timestamp = $timestamp;
+ $source_result->files['import']->last_checked = $timestamp;
+
+ }
}
else {
// File import failed. We can delete the temporary file.
file_unmanaged_delete($file->uri);
}
}
- }
-
- if (isset($context['finished']) && $context['finished'] == 1) {
- $context['results']['sources'][$source->name][$source->language] = $source;
+ $context['results']['sources'][$source->project][$source->language] = $source_result;
}
}
@@ -345,37 +291,25 @@ function locale_translation_batch_fetch_import($source, $options, &$context) {
*/
function locale_translation_batch_fetch_update_status(&$context) {
$results = array();
-//dpm($context['results']);
foreach ($context['results']['sources'] as $project => $langcodes) {
foreach ($langcodes as $langcode => $source) {
- // Store the state of the translation. The data of the imported file was kept
- // in $context['results']['sources'] will now be stored in the database.
+ // Store the state of the translation in {locale_file} table. During the
+ // batch execution the data of imported file is kept in
+ // $context['results']['sources'] will now be stored in the database.
if (isset($source->files['import'])) {
$file = $source->files['import'];
- /* {locale_file} contains:
- - project
- - langcode
- - filename
- - version
- - uri
- - last_checked
- - timestamp
- */
- $file->project = $source->project;
- $file->langcode = $source->language;
- $file->version = $source->version;
- locale_translate_update_file_history($file);
+ locale_translation_update_file_history($file);
+ // @todo Unset. Leave it disabled for debugging.
//unset($source->files['import']);
}
+ // @todo Unset. Leave it disabled for debugging.
//unset($source->files['download']);
- //$results[$project][$langcode] = $source;
-
- // Compare sources and translation history again. Translations have been
- // imported which will result in a different translation status.
- // @todo Compare and save the translation state.
- //locale_translation_status_save($data);
+ // The source data is now up to date. Data of local and/or remote source
+ // file is up to date including an updated time stamp. In a next batch
+ // operation this can be use to update the translation status.
+ $context['results']['sources'][$project][$langcode] = $source;
}
}
}
@@ -389,99 +323,24 @@ function locale_translation_batch_fetch_update_status(&$context) {
* Batch results.
*/
function locale_translation_batch_fetch_finished($success, $results) {
- $totals = array(); // Sum of added, updated and deleted translations.
- $total_skip = 0; // Sum of skipped translations
- $messages = array(); // User feedback messages.
- $project_fail = $project_success = array(); // Project names of succesfull and failed imports.
- $t = get_t();
-return;
- if ($success) {
- // Summarize results of added, updated, deleted and skiped translations.
- // Added, updated and deleted are summarized per language to be displayed accordingly.
- foreach ($results as $result) {
- if (isset($result['fail'])) {
- // Collect project names of the failed imports.
- $project_fail[$result['file']->name] = $result['file']->name;
- }
- else {
- $language = $result['language'];
-
- // Initialize variables to prevent PHP Notices.
- if (!isset($totals[$language])) {
- $totals[$language] = array();
- $totals[$language]['add'] = $totals[$language]['update'] = $totals[$language]['delete'] = 0;
- }
-
- // Summarize added, updated, deleted and skiped translations.
- $totals[$language]['add'] += $result['add'];
- $totals[$language]['update'] += $result['update'];
- $totals[$language]['delete'] += $result['delete'];
- $total_skip += $result['skip'];
-
- // Collect project names of the succesfull imports.
- $project_success[$result['file']->name] = $result['file']->name;
- }
- }
-
- // Messages of succesfull translation update results.
- if ($project_success) {
- $messages[] = format_plural(count($project_success), 'One project updated: @projects.', '@count projects updated: @projects.', array('@projects' => implode(', ', $project_success)));
- $languages = locale_translatable_language_list();
- foreach ($totals as $langcode => $total) {
- $messages[] = $t('%language translation strings added: !add, updated: !update, deleted: !delete.', array(
- '%language' => $languages[$langcode]->name,
- '!add' => $total['add'],
- '!update' => $total['update'],
- '!delete' => $total['delete'],
- ));
- }
- drupal_set_message(implode("
\n", $messages));
-
- // Warning for disallowed HTML.
- if ($total_skip) {
- drupal_set_message(
- format_plural(
- $total_skip,
- 'One translation string was skipped because it contains disallowed HTML. See !log_messages for details.',
- '@count translation strings were skipped because they contain disallowed HTML. See !log_messages for details.',
- array('!log_messages' => l(t('Recent log messages'), 'admin/reports/dblog'))),
- 'warning');
- }
-
- // Clear cache and force refresh of JavaScript translations.
- _locale_invalidate_js();
- cache()->invalidateTags(array('locale' => TRUE));
- }
-
- // Error for failed imports.
- if ($project_fail) {
- drupal_set_message(
- format_plural(
- count($project_fail),
- 'Translations of one project were not imported: @projects.',
- 'Translations of @count projects were not imported: @projects',
- array('@projects' => implode(', ', $project_fail))),
- 'error');
- }
- }
- else {
- drupal_set_message($t('Error importing translations.'), 'error');
- }
+ // @todo move locale_translate_batch_finished() into locale.batch.inc.
+ module_load_include('bulk.inc', 'locale');
+ return locale_translate_batch_finished($success, $results);
}
/**
* Check if remote file exists and when it was last updated.
*
- * @param string $url
- * URL of remote file.
+ * @param string $uri
+ * URI of remote file.
* @param array $headers
* HTTP request headers.
* @return stdClass
* Result object containing the HTTP request headers, response code, headers,
* data, redirect status and updated timestamp.
*/
-function locale_translation_http_check($url, $headers = array()) {
- $result = drupal_http_request($url, array('headers' => $headers, 'method' => 'HEAD'));
+function locale_translation_http_check($uri, $headers = array()) {
+ $result = drupal_http_request($uri, array('headers' => $headers, 'method' => 'HEAD'));
if ($result && $result->code == '200') {
$result->updated = isset($result->headers['last-modified']) ? strtotime($result->headers['last-modified']) : 0;
}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index a8242cf..8b27cde 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -405,13 +405,12 @@ function locale_translate_batch_build($files, $options) {
if (count($files)) {
$operations = array();
foreach ($files as $file) {
- // @todo: make the regex work with missing version.
- // @todo: move the language part to the form submit function.
+ // @todo: Check if regex works with missing project and version.
+ // @todo: move the language part to the form submit function?
// The filename is either {langcode}.po or {prefix}.{langcode}.po.
// Try to get the project name, project version and language code file name.
// The filename is either {langcode}.po or {project}-{version}.{langcode}.po.
// We use the language from either the $options or the file name.
- // @todo Perhaps more strict on the langcode syntax?
preg_match('!([a-z_]+)\-([0-9a-z\.\-\+]+)\.([^\./]+)\.po$!', $file->filename, $matches);
if (is_array($matches)) {
$file->project = isset($matches[1]) ? $matches[1] : '';
@@ -443,6 +442,7 @@ function locale_translate_batch_build($files, $options) {
/**
* Perform interface translation import as a batch step.
*
+ * @todo Rework description.
* The given filepath is matched against ending with '{langcode}.po'. When
* matched the filepath is added to batch context.
*
@@ -486,6 +486,7 @@ function locale_translate_batch_import($file, $options, &$context) {
// If not yet finished with reading, mark progress based on size and
// position.
if ($report['seek'] < filesize($file->uri)) {
+
$context['sandbox']['parse_state']['seek'] = $report['seek'];
// Maximize the progress bar at 95% before completion, the batch API
// could trigger the end of the operation before file reading is done,
@@ -521,6 +522,7 @@ function locale_translate_batch_import($file, $options, &$context) {
elseif (is_array($value)) {
$context['results']['stats'][$file->uri] += array($key => array());
$context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
+ }
if (is_numeric($report[$key])) {
if (!isset($context['results']['stats'][$file->uri][$key])) {
$context['results']['stats'][$file->uri][$key] = 0;
@@ -531,7 +533,8 @@ function locale_translate_batch_import($file, $options, &$context) {
}
catch (Exception $exception) {
// Import failed. Store the data of the failing file.
- $context['results']['failed_files'][$file->uri] = $file;
+ $context['results']['failed_files'][] = $file;
+ watchdog('locale', 'Unable to import translations file: @file', array('@file' => $file->uri));
}
}
}
@@ -545,7 +548,7 @@ function locale_translate_batch_import($file, $options, &$context) {
function locale_translate_batch_import_save($context) {
foreach ($context['results']['files'] as $file) {
$file->last_checked = $file->timestamp;
- locale_translate_update_file_history($file);
+ locale_translation_update_file_history($file);
}
}
@@ -556,42 +559,48 @@ function locale_translate_batch_finished($success, $results) {
if ($success) {
$additions = $updates = $deletes = $skips = 0;
$strings = $langcodes = array();
- if (isset($results['files'])) {
- drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
- }
if (isset($results['failed_files'])) {
- drupal_set_message(format_plural(count($results['failed_files']), 'One translation file could not be imported.', '@count translation files could not be imported.'), 'error');
- }
- $skipped_files = array();
- // If there are no results and/or no stats (eg. coping with an empty .po
- // file), simply do nothing.
- if ($results && isset($results['stats'])) {
- foreach ($results['stats'] as $filepath => $report) {
- $additions += $report['additions'];
- $updates += $report['updates'];
- $deletes += $report['deletes'];
- $skips += $report['skips'];
- if ($report['skips'] > 0) {
- $skipped_files[] = $filepath;
+ if (module_exists('dblog')) {
+ $message = format_plural(count($results['failed_files']), 'One translation file could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.', array('@url' => url('admin/reports/dblog')));
}
- $strings = array_merge($strings, $report['strings']);
- }
- // Get list of unique string identifiers and language codes updated.
- $strings = array_unique($strings);
- $langcodes = array_unique(array_values($results['languages']));
+ else {
+ $message = format_plural(count($results['failed_files']), 'One translation files could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.');
+ }
+ drupal_set_message($message, 'error');
}
- drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
- watchdog('locale', 'The translation was succesfully imported. %number new strings added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
- if ($skips) {
- if (module_exists('dblog')) {
- $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
+ if (isset($results['files'])) {
+ drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
+
+ $skipped_files = array();
+ // If there are no results and/or no stats (eg. coping with an empty .po
+ // file), simply do nothing.
+ if ($results && isset($results['stats'])) {
+ foreach ($results['stats'] as $filepath => $report) {
+ $additions += $report['additions'];
+ $updates += $report['updates'];
+ $deletes += $report['deletes'];
+ $skips += $report['skips'];
+ if ($report['skips'] > 0) {
+ $skipped_files[] = $filepath;
+ }
+ $strings = array_merge($strings, $report['strings']);
+ }
+ // Get list of unique string identifiers and language codes updated.
+ $strings = array_unique($strings);
+ $langcodes = array_unique(array_values($results['languages']));
}
- else {
- $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+ drupal_set_message(t('The translations were successfully imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+ watchdog('locale', 'The translations were successfully imported. %number translations added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+ if ($skips) {
+ if (module_exists('dblog')) {
+ $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
+ }
+ else {
+ $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+ }
+ drupal_set_message($message, 'error');
+ watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
}
- drupal_set_message($skip_message, 'error');
- watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
- }
if ($strings) {
// Clear cache and force refresh of JavaScript translations.
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index e69ce1f..13604c6 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -261,7 +261,7 @@ function _locale_translation_prepare_project_list($data, $type) {
*
* @return array
* Array of server parameters:
- * - "server_pattern": URL containing po file pattern.
+ * - "server_pattern": URI containing po file pattern.
*/
function locale_translation_default_translation_server() {
$config = config('locale.settings');
@@ -345,12 +345,47 @@ function locale_translation_check_projects_batch($projects, $langcodes = NULL) {
}
// Build and set the batch process.
- module_load_include('batch.inc', 'locale');
$batch = locale_translation_batch_status_build($sources);
batch_set($batch);
}
/**
+ * Build a batch to get the status of remote and local translation files.
+ *
+ * The batch process fetches the state of both remote and (if configured) local
+ * translation files. The data of the most recent translation is stored per
+ * per project and per language. This data is stored in a state variable
+ * 'locale_translation_status'. The timestamp it was last updated is stored
+ * in the state variable 'locale_translation_status_last_update'.
+ *
+ * @param array $sources
+ * Array of translation source objects for which to check the state of
+ * translation source files.
+ */
+function locale_translation_batch_status_build($sources) {
+ $operations = array();
+
+ // Set the batch processes for remote sources.
+ foreach ($sources as $source) {
+ $operations[] = array('locale_translation_batch_status_fetch_remote', array($source));
+ }
+
+ // Check for local sources, compare the results of local and remote and store
+ // the most recent.
+ $operations[] = array('locale_translation_batch_status_fetch_local', array($sources));
+ $operations[] = array('locale_translation_batch_status_compare', array());
+
+ $batch = array(
+ 'operations' => $operations,
+ 'title' => t('Checking available translations'),
+ 'finished' => 'locale_translation_batch_status_finished',
+ 'error_message' => t('Error checking available interface translation updates.'),
+ 'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
+ );
+ return $batch;
+}
+
+/**
* Check and store the status and timestamp of local po files.
*
* Only po files in the local file system are checked. Any remote translation
@@ -381,8 +416,8 @@ function locale_translation_check_projects_local($projects, $langcodes = NULL) {
if (locale_translation_source_check_file($source)) {
$source->type = 'local';
$source->timestamp = $source->files['local']->timestamp;
- $results[$name][$langcode] = $source;
}
+ $results[$name][$langcode] = $source;
}
}
@@ -427,7 +462,7 @@ function locale_translation_source_check_file(&$source) {
$directory = str_replace($scheme . '://', drupal_realpath($scheme . '://'), $directory);
}
- if ($files = file_scan_directory($directory, $filename, array('key' => 'name'))) {
+ if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
$file = current($files);
$source->files['local']->uri = $file->uri;
$source->files['local']->timestamp = filemtime($file->uri);
@@ -461,15 +496,19 @@ function locale_translation_source_check_file(&$source) {
* - "type": Most recent file type 'remote' or 'local'. Corresponding with
* a key of the "files" array.
* - "timestamp": Timestamp of the most recent translation file.
- * The "files" array contains file objects with the following properties:
- * - "uri": Local file path.
- * - "url": Remote file URL for downloads.
+ * The "files" array can hold file objects of type: 'local', 'remote',
+ * 'download', 'import' and 'current'. Each contains following properties:
+ * - "type": The object type ('local', 'remote', etc. see above).
+ * - "project": Project name.
+ * - "langcode": Language code.
+ * - "version": Project version.
+ * - "uri": Local or remote file path.
* - "directory": Directory of the local po file.
* - "filename": File name.
* - "timestamp": Timestamp of the file.
* - "keep": TRUE to keep the downloaded file.
*/
-// @todo Move this file?
+// @todo Move this function?
function locale_translation_source_build($project, $langcode, $filename = NULL) {
// Create a source object with data of the project object.
$source = clone $project;
@@ -486,9 +525,12 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
$files = array();
if (_locale_translation_file_is_remote($source->server_pattern)) {
$files['remote'] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
'type' => 'remote',
'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
- 'url' => locale_translation_build_server_pattern($source, $source->server_pattern),
+ 'uri' => locale_translation_build_server_pattern($source, $source->server_pattern),
);
if (variable_get('locale_translate_file_directory', conf_path() . '/files/translations')) {
$files['local'] = (object) array(
@@ -496,17 +538,22 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
'langcode' => $langcode,
'version' => $project->version,
'type' => 'local',
- 'directory' => 'translations://',
'filename' => locale_translation_build_server_pattern($source, $filename),
+ 'directory' => 'translations://',
);
+ $files['local']->uri = $files['local']->directory . $files['local']->filename;
}
}
else {
$files['local'] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
'type' => 'local',
- 'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)),
'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
+ 'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)),
);
+ $files['local']->uri = $files['local']->directory . '/' . $files['local']->filename;
}
$source->files = $files;
@@ -516,14 +563,14 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
/**
* Determine if a file is a remote file.
*
- * @param string $url
- * The URL or URL pattern of the file.
+ * @param string $uri
+ * The URI or URI pattern of the file.
*
* @return boolean
- * TRUE if the $url is a remote file.
+ * TRUE if the $uri is a remote file.
*/
-function _locale_translation_file_is_remote($url) {
- $scheme = file_uri_scheme($url);
+function _locale_translation_file_is_remote($uri) {
+ $scheme = file_uri_scheme($uri);
if ($scheme) {
return !drupal_realpath($scheme . '://');
}
diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc
index d053bce..e628faa 100755
--- a/core/modules/locale/locale.fetch.inc
+++ b/core/modules/locale/locale.fetch.inc
@@ -1,81 +1,20 @@
$file->project, ':language' => $file->language))->fetchField()) {
- $update = array('project', 'language');
- }
- else {
- $update = array();
- }
- return drupal_write_record('locale_file', $file, $update);
-}
-
-/**
- * Delete the history of downloaded translations.
- *
- * @param string $langcode
- * Language code of the file history to be deleted.
- */
-function locale_translation_file_history_delete($langcode) {
- db_delete('l10n_update_file')
- ->condition('language', $langcode)
- ->execute();
-}
-
-/**
- * Flag the file history as up to date.
- *
- * Compare history data in the {l10n_update_file} table with translations
- * available at translations server(s). Update the 'last_checked' timestamp of
- * the files which are up to date.
- *
- * @param $available
- * Available translations as retreived from remote server.
- */
-function l10n_update_flag_history($available) {
- if ($history = l10n_update_get_history()) {
- foreach($history as $name => $project) {
- foreach ($project as $langcode => $current) {
- if (isset($available[$name][$langcode])) {
- $update = $available[$name][$langcode];
- // When the available update is equal to the current translation the current
- // is marked checked in the {l10n_update_file} table.
- if (_l10n_update_source_compare($current, $update) == 0 && $current->version == $update->version) {
- db_update('l10n_update_file')
- ->fields(array(
- 'last_checked' => REQUEST_TIME,
- ))
- ->condition('project', $current->project)
- ->condition('language', $current->language)
- ->execute();
- }
- }
- }
- }
- }
-}
-
-/**
- * @todo
+ * @param array $sources
+ * Array of project translation states.
*/
function locale_translation_batch_fetch_build($sources) {
$t = get_t();
$batch = array(
- 'operations' => locale_translation_fetch_operations($sources),
+ 'operations' => _locale_translation_fetch_operations($sources),
'title' => $t('Updating translation.'),
'init_message' => $t('Downloading and importing files.'),
'error_message' => $t('Error importing interface translations'),
@@ -86,9 +25,12 @@ function locale_translation_batch_fetch_build($sources) {
}
/**
- * @todo
+ * Helper function to construct the batch operations to fetch translations.
+ *
+ * @param array $sources
+ * Array of project translation states.
*/
-function locale_translation_fetch_operations($sources) {
+function _locale_translation_fetch_operations($sources) {
$operations = array();
$config = config('locale.settings');
// @todo Make follow up issue for different UI options. Back to radio's, not current checkboxes.
@@ -100,115 +42,55 @@ function locale_translation_fetch_operations($sources) {
);
foreach ($sources as $source) {
- if ($source->type == 'remote') {
- // Download + import
- $operations[] = array('locale_translation_batch_fetch_download', array($source));
- $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
- }
- elseif ($source->type == 'local') {
- // Import local files.
- $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
+ if (isset($source->type)) {
+ if ($source->type == 'remote') {
+ // Download + import
+ $operations[] = array('locale_translation_batch_fetch_download', array($source));
+ $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
+ }
+ elseif ($source->type == 'local') {
+ // Import local files.
+ $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
+ }
}
}
- // Update and save the source status. New translation files have been
- // downloaded, so other sources will be newer. We update the status now.
- $operations[] = array('locale_translation_batch_status_compare', array());
- // Update translation status
- $operations[] = array('locale_translation_batch_fetch_update_status', array());
+ if (count($operations)) {
+ // Update and save the translation status.
+ $operations[] = array('locale_translation_batch_fetch_update_status', array());
+ // Update and save the source status. New translation files have been
+ // downloaded, so other sources will be newer. We update the status now.
+ $operations[] = array('locale_translation_batch_status_compare', array());
+ }
return $operations;
}
/**
- * Download source file from remote server.
+ * Download source file from a remote server.
*
- * If succesful this function returns the downloaded file in two ways:
- * - As a temporary $file object
- * - As a file path on the $source->uri property.
+ * The downloaded file is stored in the temporary files directory.
*
- * @param $source
- * Source object with all parameters
- * - 'url': url to download.
- * - 'uri': alternate destination. If not present a temporary file
- * will be used and the path returned here.
+ * @param object $source_file
+ * Source file object with at least:
+ * - "uri": uri to download the file from.
+ * - "project": Project name.
+ * - "langcode": Translation language.
+ * - "version": Project version.
+ * - "filename": File name.
* @return object
- * $file object if download successful.
+ * File object if download successful.
+ * FALSE on failure.
*/
-// @todo Can we skip this (wrapper) function?
function locale_translation_download_source($source_file) {
- if ($uri = locale_translation_download_file($source_file->url)) {
+ if ($uri = system_retrieve_file($source_file->uri, 'temporary://')) {
$file = new stdClass();
+ $file->project = $source_file->project;
+ $file->langcode = $source_file->langcode;
+ $file->version = $source_file->version;
$file->type = 'download';
$file->uri = $uri;
$file->filename = $source_file->filename;
return $file;
}
-}
-
-/**
- * Get remote file and download it to a temporary path.
- *
- * @param $download_url
- * URL of remote file.
- * @param $destination
- * URL of local destination file. By default the download will be stored
- * in a temporary file.
- */
-// @todo Use system_retrieve_file() here?
-function locale_translation_download_file($download_url, $destination = NULL) {
- $t = get_t();
- $variables['%download_link'] = $download_url;
-
- // Create temporary file or use specified file destination.
- // Temporary files get a 'locale-translation-' file name prefix.
- // @todo Do we still need the ability to override $destination?
- $file = $destination ? $destination : drupal_tempnam('temporary://', 'locale-translation-');
-
- if ($file) {
- $variables['%tmpfile'] = $file;
- // We download and store the file.
- // @todo remove the timeout once we use the batch API to download the files.
- if (($contents = drupal_http_request($download_url, array('timeout' => 90))) && $contents->code == 200 && $file_result = file_put_contents($file, $contents->data)) {
- watchdog('locale', 'Successfully downloaded %download_link to %tmpfile', $variables);
- return $file;
- }
- else {
- if (isset($contents->error)) {
- watchdog('locale', 'An error occured during the download operation: %error.', array('%error' => $contents->error), WATCHDOG_ERROR);
- }
- elseif (isset($contents->code) && $contents->code != 200) {
- watchdog('locale', 'An error occured during the download operation: HTTP status code %code.', array('%code' => $contents->code), WATCHDOG_ERROR);
- }
- if (isset($file_result)) {
- // file_put_contents() was called but returned FALSE.
- watchdog('locale', 'Unable to save %download_link file to %tmpfile.', $variables, WATCHDOG_ERROR);
- }
- }
-
- }
- else {
- $variables['%tmpdir'] = file_directory_temp();
- watchdog('l10n_update', 'Error creating temporary file for download in %tmpdir. Remote file is %download_link.', $variables, WATCHDOG_ERROR);
- }
-}
-
-/**
- * Download and import remote translation file.
- *
- * @param $download_url
- * Download URL.
- * @param $locale
- * Language code.
- * @param $mode
- * Download mode. How to treat exising and modified translations.
- *
- * @return boolean
- * TRUE on success.
- */
-// @todo: Do we need this function?
-function l10n_update_download_import($download_url, $locale, $mode = LOCALE_IMPORT_OVERWRITE) {
- if ($file = l10n_update_download_file($download_url)) {
- $result = l10n_update_import_file($file, $locale, $mode);
- return $result;
- }
+ return FALSE;
}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index f77aab4..cfc4931 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -103,6 +103,11 @@
const LOCALE_TRANSLATION_DEFAULT_FILENAME = '%project-%version.%language.po';
/**
+ * The number of seconds that the translations status entry should be considered.
+ */
+const LOCALE_TRANSLATION_STATUS_TTL = 600;
+
+/**
* Implements hook_help().
*/
function locale_help($path, $arg) {
@@ -179,7 +184,8 @@ function locale_menu() {
$items['admin/reports/translations'] = array(
'title' => 'Available translation updates',
'description' => 'Get a status report about available interface translations for your installed modules and themes.',
- 'page callback' => 'locale_translation_status',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('locale_translation_status_form'),
'access arguments' => array('translate interface'),
'file' => 'locale.pages.inc',
);
@@ -745,6 +751,27 @@ function locale_preprocess_node(&$variables) {
}
/**
+ * Get current translation status from the {locale_file} table.
+ *
+ * @return array
+ * Array of translation file objects.
+ */
+// @todo Is this the right place for this function?
+function locale_translation_get_file_history() {
+ $history = &drupal_static(__FUNCTION__, array());
+
+ if (empty($history)) {
+ // Get file history from the database.
+ $result = db_query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}');
+ foreach ($result as $file) {
+ $file->type = 'current';
+ $history[$file->project][$file->langcode] = $file;
+ }
+ }
+ return $history;
+}
+
+/**
* Update the {locale_file} table.
*
* @param $file
@@ -756,7 +783,7 @@ function locale_preprocess_node(&$variables) {
* @see drupal_write_record()
*/
// @todo Is this the right place for this function?
-function locale_translate_update_file_history($file) {
+function locale_translation_update_file_history($file) {
// Update or write new record.
if (db_query("SELECT project FROM {locale_file} WHERE project = :project AND langcode = :langcode", array(':project' => $file->project, ':langcode' => $file->langcode))->fetchField()) {
$update = array('project', 'langcode');
@@ -768,6 +795,18 @@ function locale_translate_update_file_history($file) {
}
/**
+ * Delete the history of downloaded translations.
+ *
+ * @param string $langcode
+ * Language code of the file history to be deleted.
+ */
+function locale_translation_file_history_delete($langcode) {
+ db_delete('locale_file')
+ ->condition('language', $langcode)
+ ->execute();
+}
+
+/**
* Save the status of translation sources in static cache.
*
* @param array $data
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 8ea8780..2386fdb 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -471,15 +471,142 @@ function locale_translation_manual_status() {
*
* @see locale_menu()
*/
-function locale_translation_status() {
+function locale_translation_status_form($form, &$form_state) {
+ module_load_include('compare.inc', 'locale');
+ //locale_translation_flush_projects();
+ $projects = locale_translation_get_projects();
$languages = locale_translatable_language_list();
+ $status = state()->get('locale_translation_status');
+
if (!$languages) {
drupal_set_message(t('No translatable languages available. Add language first.', array('@add_lanuage' => url('admin/config/regional/language'))), 'warning');
}
+ // === Start of temporay code. For development only ===
+ $last = state()->get('locale_translation_status_last_update');
+ $markup = '
'; + $markup .= $last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never'); + $markup .= ' (' . l(t('Check manually'), 'admin/reports/translations/check', array('query' => drupal_get_destination())) . ')'; + $markup .= '
'; + $form['last_checked'] = array( + '#markup' => $markup, + ); + + $table_header = array(t('Project'), t('Version'), t('Status'), t('Current version'), t('Timestamp'), t('Recommended version'), t('Timestamp'), t('Source')); + $rows = array(); + foreach ($projects as $name => $project) { + foreach ($languages as $langcode => $language) { + if (isset($status[$name][$langcode])) { + $source = $status[$name][$langcode]; + $row = array( + 'project' => $name, + 'version' => $source->version, + 'status' => isset($source->type) ? $source->type == 'current' ? t('Up to date') : t('Translations available') : t('Unknown'), + 'current version' => '', + 'current timestamp' => '', + 'recommended version' => '', + 'recommended timestamp' => '', + 'recommended source' => '', + ); + if (isset($source->files['current'])) { + $current = $source->files['current']; + $row['current version'] = $current->version; + $row['current timestamp'] = format_date($current->timestamp, 'custom', 'j M - G:i:s'); + } + if ($row['status'] == t('Translations available')) { + $available = $source->files[$source->type]; + $row['recommended version'] = $available->version; + $row['recommended timestamp'] = format_date($available->timestamp, 'custom', 'j M - G:i:s'); + $row['recommended source'] = $available->type; + } + } + else { + $row = array( + 'project' => $project, + 'version' => $source->version, + 'status' => t('Unknown'), + 'current version' => '', + 'current timestamp' => '', + 'recommended version' => '', + 'recommended timestamp' => '', + 'recommended source' => '', + ); + } + $rows[] = $row; + } + } + $form['table'] = array( + '#markup' => theme('table', array('header' => $table_header, 'rows' => $rows)), + ); + // @todo Calculate and display the translation status here. See the follow-up // issue for translation interface: http://drupal.org/node/1804702 - return 'TODO: Show the translation status here'; + // This form content is temporary and required to get the tests working. + $options = array(); + foreach ($projects as $name => $project) { + $options[$name] = $project->name; + } + $form['projects'] = array( + '#title' => t('Projects'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => drupal_map_assoc(array_keys($options)), + '#required' => TRUE, + ); + + $options = array(); + foreach ($languages as $langcode => $language) { + $options[$langcode] = $language->name; + } + $form['langcodes'] = array( + '#title' => t('Languages'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => drupal_map_assoc(array_keys($options)), + '#required' => TRUE, + ); + + // === End of temporay code. For development only === + + $form['actions'] = array( + '#type' => 'actions' + ); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Update') + ); + return $form; +} + +/** + * Form submission handler for locale_translation_status(). + */ +function locale_translation_status_form_submit($form, &$form_state) { + $langcodes = array_filter($form_state['values']['langcodes']); + $projects = array_filter($form_state['values']['projects']); + + // If the status was updated recently we can immediately start updating. If + // the status is not expired we will get the status first. + $last_checked = state()->get('locale_translation_status_last_update'); + if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) { + locale_translation_clear_status(); + // @todo Batch with check status + download + import. + } + else { + module_load_include('fetch.inc', 'locale'); + $status = state()->get('locale_translation_status'); + // Use only the selected projects and languages for update. + $sources = array(); + foreach($projects as $project) { + foreach ($langcodes as $langcode) { + if (isset($status[$project][$langcode])) { + $sources[] = $status[$project][$langcode]; + } + } + } + $batch = locale_translation_batch_fetch_build($sources); + batch_set($batch); + } } /**