annotate core/modules/locale/locale.batch.inc @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Batch process to check the availability of remote or local po files.
Chris@0 6 */
Chris@0 7
Chris@18 8 use Drupal\Core\Url;
Chris@0 9 use GuzzleHttp\Exception\RequestException;
Chris@0 10 use Psr\Http\Message\RequestInterface;
Chris@0 11 use Psr\Http\Message\ResponseInterface;
Chris@0 12 use Psr\Http\Message\UriInterface;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Load the common translation API.
Chris@0 16 */
Chris@0 17 // @todo Combine functions differently in files to avoid unnecessary includes.
Chris@0 18 // Follow-up issue: https://www.drupal.org/node/1834298.
Chris@0 19 require_once __DIR__ . '/locale.translation.inc';
Chris@0 20
Chris@0 21 /**
Chris@0 22 * Implements callback_batch_operation().
Chris@0 23 *
Chris@0 24 * Checks the presence and creation time po translation files in located at
Chris@0 25 * remote server location and local file system.
Chris@0 26 *
Chris@0 27 * @param string $project
Chris@0 28 * Machine name of the project for which to check the translation status.
Chris@0 29 * @param string $langcode
Chris@0 30 * Language code of the language for which to check the translation.
Chris@0 31 * @param array $options
Chris@0 32 * An array with options that can have the following elements:
Chris@0 33 * - 'finish_feedback': Whether or not to give feedback to the user when the
Chris@0 34 * batch is finished. Optional, defaults to TRUE.
Chris@0 35 * - 'use_remote': Whether or not to check the remote translation file.
Chris@0 36 * Optional, defaults to TRUE.
Chris@0 37 * @param array|\ArrayAccess $context
Chris@0 38 * The batch context.
Chris@0 39 */
Chris@0 40 function locale_translation_batch_status_check($project, $langcode, array $options, &$context) {
Chris@0 41 $failure = $checked = FALSE;
Chris@0 42 $options += [
Chris@0 43 'finish_feedback' => TRUE,
Chris@0 44 'use_remote' => TRUE,
Chris@0 45 ];
Chris@0 46 $source = locale_translation_get_status([$project], [$langcode]);
Chris@0 47 $source = $source[$project][$langcode];
Chris@0 48
Chris@0 49 // Check the status of local translation files.
Chris@0 50 if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
Chris@0 51 if ($file = locale_translation_source_check_file($source)) {
Chris@0 52 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
Chris@0 53 }
Chris@0 54 $checked = TRUE;
Chris@0 55 }
Chris@0 56
Chris@0 57 // Check the status of remote translation files.
Chris@0 58 if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
Chris@0 59 $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
Chris@0 60 if ($result = locale_translation_http_check($remote_file->uri)) {
Chris@0 61 // Update the file object with the result data. In case of a redirect we
Chris@0 62 // store the resulting uri.
Chris@0 63 if (isset($result['last_modified'])) {
Chris@0 64 $remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri;
Chris@0 65 $remote_file->timestamp = $result['last_modified'];
Chris@0 66 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file);
Chris@0 67 }
Chris@0 68 // @todo What to do with when the file is not found (404)? To prevent
Chris@0 69 // re-checking within the TTL (1day, 1week) we can set a last_checked
Chris@0 70 // timestamp or cache the result.
Chris@0 71 $checked = TRUE;
Chris@0 72 }
Chris@0 73 else {
Chris@0 74 $failure = TRUE;
Chris@0 75 }
Chris@0 76 }
Chris@0 77
Chris@0 78 // Provide user feedback and record success or failure for reporting at the
Chris@0 79 // end of the batch.
Chris@0 80 if ($options['finish_feedback'] && $checked) {
Chris@0 81 $context['results']['files'][] = $source->name;
Chris@0 82 }
Chris@0 83 if ($failure && !$checked) {
Chris@0 84 $context['results']['failed_files'][] = $source->name;
Chris@0 85 }
Chris@0 86 $context['message'] = t('Checked translation for %project.', ['%project' => $source->project]);
Chris@0 87 }
Chris@0 88
Chris@0 89 /**
Chris@0 90 * Implements callback_batch_finished().
Chris@0 91 *
Chris@0 92 * Set result message.
Chris@0 93 *
Chris@0 94 * @param bool $success
Chris@0 95 * TRUE if batch successfully completed.
Chris@0 96 * @param array $results
Chris@0 97 * Batch results.
Chris@0 98 */
Chris@0 99 function locale_translation_batch_status_finished($success, $results) {
Chris@0 100 if ($success) {
Chris@0 101 if (isset($results['failed_files'])) {
Chris@0 102 if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) {
Chris@18 103 $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' => Url::fromRoute('dblog.overview')->toString()]);
Chris@0 104 }
Chris@0 105 else {
Chris@0 106 $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.');
Chris@0 107 }
Chris@17 108 \Drupal::messenger()->addError($message);
Chris@0 109 }
Chris@0 110 if (isset($results['files'])) {
Chris@17 111 \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural(
Chris@0 112 count($results['files']),
Chris@0 113 'Checked available interface translation updates for one project.',
Chris@0 114 'Checked available interface translation updates for @count projects.'
Chris@0 115 ));
Chris@0 116 }
Chris@0 117 if (!isset($results['failed_files']) && !isset($results['files'])) {
Chris@17 118 \Drupal::messenger()->addStatus(t('Nothing to check.'));
Chris@0 119 }
Chris@0 120 \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
Chris@0 121 }
Chris@0 122 else {
Chris@17 123 \Drupal::messenger()->addError(t('An error occurred trying to check available interface translation updates.'));
Chris@0 124 }
Chris@0 125 }
Chris@0 126
Chris@0 127 /**
Chris@0 128 * Implements callback_batch_operation().
Chris@0 129 *
Chris@0 130 * Downloads a remote gettext file into the translations directory. When
Chris@0 131 * successfully the translation status is updated.
Chris@0 132 *
Chris@0 133 * @param object $project
Chris@0 134 * Source object of the translatable project.
Chris@0 135 * @param string $langcode
Chris@0 136 * Language code.
Chris@0 137 * @param array $context
Chris@0 138 * The batch context.
Chris@0 139 *
Chris@0 140 * @see locale_translation_batch_fetch_import()
Chris@0 141 */
Chris@0 142 function locale_translation_batch_fetch_download($project, $langcode, &$context) {
Chris@0 143 $sources = locale_translation_get_status([$project], [$langcode]);
Chris@0 144 if (isset($sources[$project][$langcode])) {
Chris@0 145 $source = $sources[$project][$langcode];
Chris@0 146 if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) {
Chris@0 147 if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) {
Chris@0 148 $context['message'] = t('Downloaded translation for %project.', ['%project' => $source->project]);
Chris@0 149 locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
Chris@0 150 }
Chris@0 151 else {
Chris@0 152 $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
Chris@0 153 }
Chris@0 154 }
Chris@0 155 }
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@0 159 * Implements callback_batch_operation().
Chris@0 160 *
Chris@0 161 * Imports a gettext file from the translation directory. When successfully the
Chris@0 162 * translation status is updated.
Chris@0 163 *
Chris@0 164 * @param object $project
Chris@0 165 * Source object of the translatable project.
Chris@0 166 * @param string $langcode
Chris@0 167 * Language code.
Chris@0 168 * @param array $options
Chris@0 169 * Array of import options.
Chris@0 170 * @param array $context
Chris@0 171 * The batch context.
Chris@0 172 *
Chris@0 173 * @see locale_translate_batch_import_files()
Chris@0 174 * @see locale_translation_batch_fetch_download()
Chris@0 175 */
Chris@0 176 function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) {
Chris@0 177 $sources = locale_translation_get_status([$project], [$langcode]);
Chris@0 178 if (isset($sources[$project][$langcode])) {
Chris@0 179 $source = $sources[$project][$langcode];
Chris@0 180 if (isset($source->type)) {
Chris@0 181 if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) {
Chris@0 182 $file = $source->files[LOCALE_TRANSLATION_LOCAL];
Chris@0 183 module_load_include('bulk.inc', 'locale');
Chris@0 184 $options += [
Chris@0 185 'message' => t('Importing translation for %project.', ['%project' => $source->project]),
Chris@0 186 ];
Chris@0 187 // Import the translation file. For large files the batch operations is
Chris@0 188 // progressive and will be called repeatedly until finished.
Chris@0 189 locale_translate_batch_import($file, $options, $context);
Chris@0 190
Chris@0 191 // The import is finished.
Chris@0 192 if (isset($context['finished']) && $context['finished'] == 1) {
Chris@0 193 // The import is successful.
Chris@0 194 if (isset($context['results']['files'][$file->uri])) {
Chris@0 195 $context['message'] = t('Imported translation for %project.', ['%project' => $source->project]);
Chris@0 196
Chris@0 197 // Save the data of imported source into the {locale_file} table and
Chris@0 198 // update the current translation status.
Chris@0 199 locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]);
Chris@0 200 }
Chris@0 201 }
Chris@0 202 }
Chris@0 203 }
Chris@0 204 }
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * Implements callback_batch_finished().
Chris@0 209 *
Chris@0 210 * Set result message.
Chris@0 211 *
Chris@0 212 * @param bool $success
Chris@0 213 * TRUE if batch successfully completed.
Chris@0 214 * @param array $results
Chris@0 215 * Batch results.
Chris@0 216 */
Chris@0 217 function locale_translation_batch_fetch_finished($success, $results) {
Chris@0 218 module_load_include('bulk.inc', 'locale');
Chris@0 219 if ($success) {
Chris@0 220 \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
Chris@0 221 }
Chris@0 222 return locale_translate_batch_finished($success, $results);
Chris@0 223 }
Chris@0 224
Chris@0 225 /**
Chris@0 226 * Check if remote file exists and when it was last updated.
Chris@0 227 *
Chris@0 228 * @param string $uri
Chris@0 229 * URI of remote file.
Chris@0 230 *
Chris@0 231 * @return array|bool
Chris@0 232 * Associative array of file data with the following elements:
Chris@0 233 * - last_modified: Last modified timestamp of the translation file.
Chris@0 234 * - (optional) location: The location of the translation file. Is only set
Chris@0 235 * when a redirect (301) has occurred.
Chris@0 236 * TRUE if the file is not found. FALSE if a fault occurred.
Chris@0 237 */
Chris@0 238 function locale_translation_http_check($uri) {
Chris@0 239 $logger = \Drupal::logger('locale');
Chris@0 240 try {
Chris@0 241 $actual_uri = NULL;
Chris@0 242 $response = \Drupal::service('http_client_factory')->fromOptions([
Chris@0 243 'allow_redirects' => [
Chris@0 244 'on_redirect' => function (RequestInterface $request, ResponseInterface $response, UriInterface $request_uri) use (&$actual_uri) {
Chris@0 245 $actual_uri = (string) $request_uri;
Chris@17 246 },
Chris@0 247 ],
Chris@0 248 ])->head($uri);
Chris@0 249 $result = [];
Chris@0 250
Chris@0 251 // Return the effective URL if it differs from the requested.
Chris@0 252 if ($actual_uri && $actual_uri !== $uri) {
Chris@0 253 $result['location'] = $actual_uri;
Chris@0 254 }
Chris@0 255
Chris@0 256 $result['last_modified'] = $response->hasHeader('Last-Modified') ? strtotime($response->getHeaderLine('Last-Modified')) : 0;
Chris@0 257 return $result;
Chris@0 258 }
Chris@0 259 catch (RequestException $e) {
Chris@0 260 // Handle 4xx and 5xx http responses.
Chris@0 261 if ($response = $e->getResponse()) {
Chris@0 262 if ($response->getStatusCode() == 404) {
Chris@0 263 // File not found occurs when a translation file is not yet available
Chris@0 264 // at the translation server. But also if a custom module or custom
Chris@0 265 // theme does not define the location of a translation file. By default
Chris@0 266 // the file is checked at the translation server, but it will not be
Chris@0 267 // found there.
Chris@0 268 $logger->notice('Translation file not found: @uri.', ['@uri' => $uri]);
Chris@0 269 return TRUE;
Chris@0 270 }
Chris@0 271 $logger->notice('HTTP request to @url failed with error: @error.', ['@url' => $uri, '@error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()]);
Chris@0 272 }
Chris@0 273 }
Chris@0 274
Chris@0 275 return FALSE;
Chris@0 276 }
Chris@0 277
Chris@0 278 /**
Chris@0 279 * Downloads a translation file from a remote server.
Chris@0 280 *
Chris@0 281 * @param object $source_file
Chris@0 282 * Source file object with at least:
Chris@0 283 * - "uri": uri to download the file from.
Chris@0 284 * - "project": Project name.
Chris@0 285 * - "langcode": Translation language.
Chris@0 286 * - "version": Project version.
Chris@0 287 * - "filename": File name.
Chris@0 288 * @param string $directory
Chris@0 289 * Directory where the downloaded file will be saved. Defaults to the
Chris@0 290 * temporary file path.
Chris@0 291 *
Chris@0 292 * @return object
Chris@0 293 * File object if download was successful. FALSE on failure.
Chris@0 294 */
Chris@0 295 function locale_translation_download_source($source_file, $directory = 'temporary://') {
Chris@16 296 if ($uri = system_retrieve_file($source_file->uri, $directory, FALSE, FILE_EXISTS_REPLACE)) {
Chris@0 297 $file = clone($source_file);
Chris@0 298 $file->type = LOCALE_TRANSLATION_LOCAL;
Chris@0 299 $file->uri = $uri;
Chris@0 300 $file->directory = $directory;
Chris@0 301 $file->timestamp = filemtime($uri);
Chris@0 302 return $file;
Chris@0 303 }
Chris@0 304 \Drupal::logger('locale')->error('Unable to download translation file @uri.', ['@uri' => $source_file->uri]);
Chris@0 305 return FALSE;
Chris@0 306 }