5 * Batch process to check the availability of remote or local po files.
8 use GuzzleHttp\Exception\RequestException;
9 use Psr\Http\Message\RequestInterface;
10 use Psr\Http\Message\ResponseInterface;
11 use Psr\Http\Message\UriInterface;
14 * Load the common translation API.
16 // @todo Combine functions differently in files to avoid unnecessary includes.
17 // Follow-up issue: https://www.drupal.org/node/1834298.
18 require_once __DIR__ . '/locale.translation.inc';
21 * Implements callback_batch_operation().
23 * Checks the presence and creation time po translation files in located at
24 * remote server location and local file system.
26 * @param string $project
27 * Machine name of the project for which to check the translation status.
28 * @param string $langcode
29 * Language code of the language for which to check the translation.
30 * @param array $options
31 * An array with options that can have the following elements:
32 * - 'finish_feedback': Whether or not to give feedback to the user when the
33 * batch is finished. Optional, defaults to TRUE.
34 * - 'use_remote': Whether or not to check the remote translation file.
35 * Optional, defaults to TRUE.
36 * @param array|\ArrayAccess $context
39 function locale_translation_batch_status_check($project, $langcode, array $options, &$context) {
40 $failure = $checked = FALSE;
42 'finish_feedback' => TRUE,
45 $source = locale_translation_get_status([$project], [$langcode]);
46 $source = $source[$project][$langcode];
48 // Check the status of local translation files.
49 if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
50 if ($file = locale_translation_source_check_file($source)) {
51 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
56 // Check the status of remote translation files.
57 if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
58 $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
59 if ($result = locale_translation_http_check($remote_file->uri)) {
60 // Update the file object with the result data. In case of a redirect we
61 // store the resulting uri.
62 if (isset($result['last_modified'])) {
63 $remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri;
64 $remote_file->timestamp = $result['last_modified'];
65 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file);
67 // @todo What to do with when the file is not found (404)? To prevent
68 // re-checking within the TTL (1day, 1week) we can set a last_checked
69 // timestamp or cache the result.
77 // Provide user feedback and record success or failure for reporting at the
79 if ($options['finish_feedback'] && $checked) {
80 $context['results']['files'][] = $source->name;
82 if ($failure && !$checked) {
83 $context['results']['failed_files'][] = $source->name;
85 $context['message'] = t('Checked translation for %project.', ['%project' => $source->project]);
89 * Implements callback_batch_finished().
93 * @param bool $success
94 * TRUE if batch successfully completed.
95 * @param array $results
98 function locale_translation_batch_status_finished($success, $results) {
100 if (isset($results['failed_files'])) {
101 if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) {
102 $message = \Drupal::translation()->formatPlural(count($results['failed_files']), 'One translation file could not be checked. <a href=":url">See the log</a> for details.', '@count translation files could not be checked. <a href=":url">See the log</a> for details.', [':url' => \Drupal::url('dblog.overview')]);
105 $message = \Drupal::translation()->formatPlural(count($results['failed_files']), 'One translation files could not be checked. See the log for details.', '@count translation files could not be checked. See the log for details.');
107 drupal_set_message($message, 'error');
109 if (isset($results['files'])) {
110 drupal_set_message(\Drupal::translation()->formatPlural(
111 count($results['files']),
112 'Checked available interface translation updates for one project.',
113 'Checked available interface translation updates for @count projects.'
116 if (!isset($results['failed_files']) && !isset($results['files'])) {
117 drupal_set_message(t('Nothing to check.'));
119 \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
122 drupal_set_message(t('An error occurred trying to check available interface translation updates.'), 'error');
127 * Implements callback_batch_operation().
129 * Downloads a remote gettext file into the translations directory. When
130 * successfully the translation status is updated.
132 * @param object $project
133 * Source object of the translatable project.
134 * @param string $langcode
136 * @param array $context
139 * @see locale_translation_batch_fetch_import()
141 function locale_translation_batch_fetch_download($project, $langcode, &$context) {
142 $sources = locale_translation_get_status([$project], [$langcode]);
143 if (isset($sources[$project][$langcode])) {
144 $source = $sources[$project][$langcode];
145 if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) {
146 if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) {
147 $context['message'] = t('Downloaded translation for %project.', ['%project' => $source->project]);
148 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
151 $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
158 * Implements callback_batch_operation().
160 * Imports a gettext file from the translation directory. When successfully the
161 * translation status is updated.
163 * @param object $project
164 * Source object of the translatable project.
165 * @param string $langcode
167 * @param array $options
168 * Array of import options.
169 * @param array $context
172 * @see locale_translate_batch_import_files()
173 * @see locale_translation_batch_fetch_download()
175 function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) {
176 $sources = locale_translation_get_status([$project], [$langcode]);
177 if (isset($sources[$project][$langcode])) {
178 $source = $sources[$project][$langcode];
179 if (isset($source->type)) {
180 if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) {
181 $file = $source->files[LOCALE_TRANSLATION_LOCAL];
182 module_load_include('bulk.inc', 'locale');
184 'message' => t('Importing translation for %project.', ['%project' => $source->project]),
186 // Import the translation file. For large files the batch operations is
187 // progressive and will be called repeatedly until finished.
188 locale_translate_batch_import($file, $options, $context);
190 // The import is finished.
191 if (isset($context['finished']) && $context['finished'] == 1) {
192 // The import is successful.
193 if (isset($context['results']['files'][$file->uri])) {
194 $context['message'] = t('Imported translation for %project.', ['%project' => $source->project]);
196 // Save the data of imported source into the {locale_file} table and
197 // update the current translation status.
198 locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]);
207 * Implements callback_batch_finished().
209 * Set result message.
211 * @param bool $success
212 * TRUE if batch successfully completed.
213 * @param array $results
216 function locale_translation_batch_fetch_finished($success, $results) {
217 module_load_include('bulk.inc', 'locale');
219 \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
221 return locale_translate_batch_finished($success, $results);
225 * Check if remote file exists and when it was last updated.
228 * URI of remote file.
231 * Associative array of file data with the following elements:
232 * - last_modified: Last modified timestamp of the translation file.
233 * - (optional) location: The location of the translation file. Is only set
234 * when a redirect (301) has occurred.
235 * TRUE if the file is not found. FALSE if a fault occurred.
237 function locale_translation_http_check($uri) {
238 $logger = \Drupal::logger('locale');
241 $response = \Drupal::service('http_client_factory')->fromOptions(['allow_redirects' => [
242 'on_redirect' => function(RequestInterface $request, ResponseInterface $response, UriInterface $request_uri) use (&$actual_uri) {
243 $actual_uri = (string) $request_uri;
248 // Return the effective URL if it differs from the requested.
249 if ($actual_uri && $actual_uri !== $uri) {
250 $result['location'] = $actual_uri;
253 $result['last_modified'] = $response->hasHeader('Last-Modified') ? strtotime($response->getHeaderLine('Last-Modified')) : 0;
256 catch (RequestException $e) {
257 // Handle 4xx and 5xx http responses.
258 if ($response = $e->getResponse()) {
259 if ($response->getStatusCode() == 404) {
260 // File not found occurs when a translation file is not yet available
261 // at the translation server. But also if a custom module or custom
262 // theme does not define the location of a translation file. By default
263 // the file is checked at the translation server, but it will not be
265 $logger->notice('Translation file not found: @uri.', ['@uri' => $uri]);
268 $logger->notice('HTTP request to @url failed with error: @error.', ['@url' => $uri, '@error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()]);
276 * Downloads a translation file from a remote server.
278 * @param object $source_file
279 * Source file object with at least:
280 * - "uri": uri to download the file from.
281 * - "project": Project name.
282 * - "langcode": Translation language.
283 * - "version": Project version.
284 * - "filename": File name.
285 * @param string $directory
286 * Directory where the downloaded file will be saved. Defaults to the
287 * temporary file path.
290 * File object if download was successful. FALSE on failure.
292 function locale_translation_download_source($source_file, $directory = 'temporary://') {
293 if ($uri = system_retrieve_file($source_file->uri, $directory)) {
294 $file = clone($source_file);
295 $file->type = LOCALE_TRANSLATION_LOCAL;
297 $file->directory = $directory;
298 $file->timestamp = filemtime($uri);
301 \Drupal::logger('locale')->error('Unable to download translation file @uri.', ['@uri' => $source_file->uri]);