Backup of db before drupal security update
[yaffs-website] / web / core / modules / locale / locale.batch.inc
1 <?php
2
3 /**
4  * @file
5  * Batch process to check the availability of remote or local po files.
6  */
7
8 use GuzzleHttp\Exception\RequestException;
9 use Psr\Http\Message\RequestInterface;
10 use Psr\Http\Message\ResponseInterface;
11 use Psr\Http\Message\UriInterface;
12
13 /**
14  * Load the common translation API.
15  */
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';
19
20 /**
21  * Implements callback_batch_operation().
22  *
23  * Checks the presence and creation time po translation files in located at
24  * remote server location and local file system.
25  *
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
37  *   The batch context.
38  */
39 function locale_translation_batch_status_check($project, $langcode, array $options, &$context) {
40   $failure = $checked = FALSE;
41   $options += [
42     'finish_feedback' => TRUE,
43     'use_remote' => TRUE,
44   ];
45   $source = locale_translation_get_status([$project], [$langcode]);
46   $source = $source[$project][$langcode];
47
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);
52     }
53     $checked = TRUE;
54   }
55
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);
66       }
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.
70       $checked = TRUE;
71     }
72     else {
73       $failure = TRUE;
74     }
75   }
76
77   // Provide user feedback and record success or failure for reporting at the
78   // end of the batch.
79   if ($options['finish_feedback'] && $checked) {
80     $context['results']['files'][] = $source->name;
81   }
82   if ($failure && !$checked) {
83     $context['results']['failed_files'][] = $source->name;
84   }
85   $context['message'] = t('Checked translation for %project.', ['%project' => $source->project]);
86 }
87
88 /**
89  * Implements callback_batch_finished().
90  *
91  * Set result message.
92  *
93  * @param bool $success
94  *   TRUE if batch successfully completed.
95  * @param array $results
96  *   Batch results.
97  */
98 function locale_translation_batch_status_finished($success, $results) {
99   if ($success) {
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')]);
103       }
104       else {
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.');
106       }
107       drupal_set_message($message, 'error');
108     }
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.'
114       ));
115     }
116     if (!isset($results['failed_files']) && !isset($results['files'])) {
117       drupal_set_message(t('Nothing to check.'));
118     }
119     \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
120   }
121   else {
122     drupal_set_message(t('An error occurred trying to check available interface translation updates.'), 'error');
123   }
124 }
125
126 /**
127  * Implements callback_batch_operation().
128  *
129  * Downloads a remote gettext file into the translations directory. When
130  * successfully the translation status is updated.
131  *
132  * @param object $project
133  *   Source object of the translatable project.
134  * @param string $langcode
135  *   Language code.
136  * @param array $context
137  *   The batch context.
138  *
139  * @see locale_translation_batch_fetch_import()
140  */
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);
149       }
150       else {
151         $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
152       }
153     }
154   }
155 }
156
157 /**
158  * Implements callback_batch_operation().
159  *
160  * Imports a gettext file from the translation directory. When successfully the
161  * translation status is updated.
162  *
163  * @param object $project
164  *   Source object of the translatable project.
165  * @param string $langcode
166  *   Language code.
167  * @param array $options
168  *   Array of import options.
169  * @param array $context
170  *   The batch context.
171  *
172  * @see locale_translate_batch_import_files()
173  * @see locale_translation_batch_fetch_download()
174  */
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');
183         $options += [
184           'message' => t('Importing translation for %project.', ['%project' => $source->project]),
185         ];
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);
189
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]);
195
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]);
199           }
200         }
201       }
202     }
203   }
204 }
205
206 /**
207  * Implements callback_batch_finished().
208  *
209  * Set result message.
210  *
211  * @param bool $success
212  *   TRUE if batch successfully completed.
213  * @param array $results
214  *   Batch results.
215  */
216 function locale_translation_batch_fetch_finished($success, $results) {
217   module_load_include('bulk.inc', 'locale');
218   if ($success) {
219     \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
220   }
221   return locale_translate_batch_finished($success, $results);
222 }
223
224 /**
225  * Check if remote file exists and when it was last updated.
226  *
227  * @param string $uri
228  *   URI of remote file.
229  *
230  * @return array|bool
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.
236  */
237 function locale_translation_http_check($uri) {
238   $logger = \Drupal::logger('locale');
239   try {
240     $actual_uri = NULL;
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;
244       }
245     ]])->head($uri);
246     $result = [];
247
248     // Return the effective URL if it differs from the requested.
249     if ($actual_uri && $actual_uri !== $uri) {
250       $result['location'] = $actual_uri;
251     }
252
253     $result['last_modified'] = $response->hasHeader('Last-Modified') ? strtotime($response->getHeaderLine('Last-Modified')) : 0;
254     return $result;
255   }
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
264         // found there.
265         $logger->notice('Translation file not found: @uri.', ['@uri' => $uri]);
266         return TRUE;
267       }
268       $logger->notice('HTTP request to @url failed with error: @error.', ['@url' => $uri, '@error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()]);
269     }
270   }
271
272   return FALSE;
273 }
274
275 /**
276  * Downloads a translation file from a remote server.
277  *
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.
288  *
289  * @return object
290  *   File object if download was successful. FALSE on failure.
291  */
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;
296     $file->uri = $uri;
297     $file->directory = $directory;
298     $file->timestamp = filemtime($uri);
299     return $file;
300   }
301   \Drupal::logger('locale')->error('Unable to download translation file @uri.', ['@uri' => $source_file->uri]);
302   return FALSE;
303 }