locale.batch.inc 11.2 KB
Newer Older
1 2 3 4 5 6 7
<?php

/**
 * @file
 *   Batch process to check the availability of remote or local po files.
 */

8 9 10
use Guzzle\Http\Exception\BadResponseException;
use Guzzle\Http\Exception\RequestException;

11
/**
12
 * Load the common translation API.
13
 */
14 15
// @todo Combine functions differently in files to avoid unnecessary includes.
// Follow-up issue http://drupal.org/node/1834298
16
require_once __DIR__ . '/locale.translation.inc';
17 18

/**
19
 * Batch operation callback: Check status of a remote and local po file.
20
 *
21 22
 * Checks the presence and creation time po translation files in located at
 * remote server location and local file system.
23
 *
24 25 26 27 28 29 30 31 32 33
 * @param string $project
 *   Machine name of the project for which to check the translation status.
 * @param string $langcode
 *   Language code of the language for which to check the translation.
 * @param array $options
 *   Optional, an array with options that can have the following elements:
 *   - 'finish_feedback': Whether or not to give feedback to the user when the
 *     batch is finished. Optional, defaults to TRUE.
 *   - 'use_remote': Whether or not to check the remote translation file.
 *     Optional, defaults to TRUE.
34
 * @param array $context
35
 *   The batch context.
36
*/
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
function locale_translation_batch_status_check($project, $langcode, $options = array(), &$context) {
  $failure = $checked = FALSE;
  $options += array(
    'finish_feedback' => TRUE,
    'use_remote' => TRUE,
  );
  $source = locale_translation_get_status(array($project), array($langcode));
  $source = $source[$project][$langcode];

  // Check the status of local translation files.
  if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
    if ($file = locale_translation_source_check_file($source)) {
      locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
    }
    $checked = TRUE;
  }
53

54 55 56 57
  // Check the status of remote translation files.
  if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
    $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
    if ($result = locale_translation_http_check($remote_file->uri)) {
58
      // Update the file object with the result data. In case of a redirect we
59
      // store the resulting uri.
60 61 62
      if (isset($result['last_modified'])) {
        $remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri;
        $remote_file->timestamp = $result['last_modified'];
63
        locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file);
64
      }
65 66 67 68
      // @todo What to do with when the file is not found (404)? To prevent
      //   re-checking within the TTL (1day, 1week) we can set a last_checked
      //   timestamp or cache the result.
      $checked = TRUE;
69 70
    }
    else {
71
      $failure = TRUE;
72 73 74
    }
  }

75 76 77 78
  // Provide user feedback and record success or failure for reporting at the
  // end of the batch.
  if ($options['finish_feedback'] && $checked) {
    $context['results']['files'][] = $source->name;
79
  }
80 81
  if ($failure && !$checked) {
    $context['results']['failed_files'][] = $source->name;
82
  }
83
  $context['message'] = t('Checked translation for %project.', array('%project' => $source->project));
84 85 86 87 88 89
}

/**
 * Batch finished callback: Set result message.
 *
 * @param boolean $success
90
 *   TRUE if batch successfully completed.
91 92 93 94
 * @param array $results
 *   Batch results.
 */
function locale_translation_batch_status_finished($success, $results) {
95 96
  if ($success) {
    if (isset($results['failed_files'])) {
97
        if (\Drupal::moduleHandler()->moduleExists('dblog')) {
98 99 100 101 102 103 104 105
          $message = format_plural(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.', array('@url' => url('admin/reports/dblog')));
        }
        else {
          $message = format_plural(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.');
        }
        drupal_set_message($message, 'error');
    }
    if (isset($results['files'])) {
106
      drupal_set_message(format_plural(
107
        count($results['files']),
108 109 110 111
        'Checked available interface translation updates for one project.',
        'Checked available interface translation updates for @count projects.'
      ));
    }
112 113 114
    if (!isset($results['failed_files']) && !isset($results['files'])) {
      drupal_set_message(t('Nothing to check.'));
    }
115
    \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
116 117
  }
  else {
118
    drupal_set_message(t('An error occurred trying to check available interface translation updates.'), 'error');
119 120 121
  }
}

122 123 124
/**
 * Batch operation: Download a remote translation file.
 *
125 126
 * Downloads a remote gettext file into the translations directory. When
 * successfully the translation status is updated.
127 128 129 130 131
 *
 * @param object $project
 *   Source object of the translatable project.
 * @param string $langcode
 *   Language code.
132 133
 * @param array $context
 *   The batch context.
134 135 136 137
 *
 * @see locale_translation_batch_fetch_import()
 */
function locale_translation_batch_fetch_download($project, $langcode, &$context) {
138 139 140
  $sources = locale_translation_get_status(array($project), array($langcode));
  if (isset($sources[$project][$langcode])) {
    $source = $sources[$project][$langcode];
141
    if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) {
142
      if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) {
143
        $context['message'] = t('Downloaded translation for %project.', array('%project' => $source->project));
144
        locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
145 146 147 148 149 150 151 152 153 154 155
      }
      else {
        $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
      }
    }
  }
}

/**
 * Batch process: Import translation file.
 *
156 157
 * Imports a gettext file from the translation directory. When successfully the
 * translation status is updated.
158 159 160 161 162 163 164
 *
 * @param object $project
 *   Source object of the translatable project.
 * @param string $langcode
 *   Language code.
 * @param array $options
 *   Array of import options.
165 166
 * @param array $context
 *   The batch context.
167 168 169 170 171
 *
 * @see locale_translate_batch_import_files()
 * @see locale_translation_batch_fetch_download()
 */
function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) {
172 173 174
  $sources = locale_translation_get_status(array($project), array($langcode));
  if (isset($sources[$project][$langcode])) {
    $source = $sources[$project][$langcode];
175 176
    if (isset($source->type)) {
      if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) {
177
        $file = $source->files[LOCALE_TRANSLATION_LOCAL];
178 179
        module_load_include('bulk.inc', 'locale');
        $options += array(
180
          'message' => t('Importing translation for %project.', array('%project' => $source->project)),
181 182
        );
        // Import the translation file. For large files the batch operations is
183
        // progressive and will be called repeatedly until finished.
184 185 186 187
        locale_translate_batch_import($file, $options, $context);

        // The import is finished.
        if (isset($context['finished']) && $context['finished'] == 1) {
188
          // The import is successful.
189
          if (isset($context['results']['files'][$file->uri])) {
190
            $context['message'] = t('Imported translation for %project.', array('%project' => $source->project));
191

192 193 194
            // Save the data of imported source into the {locale_file} table and
            // update the current translation status.
            locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]);
195 196 197 198 199 200 201 202 203 204 205
          }
        }
      }
    }
  }
}

/**
 * Batch finished callback: Set result message.
 *
 * @param boolean $success
206
 *   TRUE if batch successfully completed.
207 208 209 210 211
 * @param array
 *   Batch results.
 */
function locale_translation_batch_fetch_finished($success, $results) {
  module_load_include('bulk.inc', 'locale');
212
  if ($success) {
213
    \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
214
  }
215 216 217
  return locale_translate_batch_finished($success, $results);
}

218 219 220
/**
 * Check if remote file exists and when it was last updated.
 *
221 222
 * @param string $uri
 *   URI of remote file.
223 224 225 226 227 228 229
 *
 * @return array|boolean
 *   Associative array of file data with the following elements:
 *   - last_modified: Last modified timestamp of the translation file.
 *   - (optional) location: The location of the translation file. Is only set
 *     when a redirect (301) has occurred.
 *   TRUE if the file is not found. FALSE if a fault occurred.
230
 */
231 232 233
function locale_translation_http_check($uri) {

  try {
234
    $response = \Drupal::httpClient()
235 236 237 238
      ->head($uri)
      ->send();
    $result = array();

239 240 241
    // Return the effective URL if it differs from the requested.
    if ($response->getEffectiveUrl() != $uri) {
      $result['location'] = $response->getEffectiveUrl();
242
    }
243 244

    $result['last_modified'] = $response->getLastModified() ? strtotime($response->getLastModified()) : 0;
245 246
    return $result;
  }
247 248 249 250 251 252 253 254 255 256 257
  catch (BadResponseException $e) {
    // Handle 4xx and 5xx http responses.
    $response = $e->getResponse();
    if ($response->getStatusCode() == 404) {
      // File not found occurs when a translation file is not yet available
      // at the translation server. But also if a custom module or custom
      // theme does not define the location of a translation file. By default
      // the file is checked at the translation server, but it will not be
      // found there.
      watchdog('locale', 'Translation file not found: @uri.', array('@uri' => $uri));
      return TRUE;
258
    }
259 260 261 262 263 264
    watchdog('locale', 'HTTP request to @url failed with error: @error.', array('@url' => $uri, '@error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()));
  }
  catch (RequestException $e) {
    // Handle connection problems and cURL specific errors (CurlException) and
    // other http related errors.
    watchdog('locale', 'HTTP request to @url failed with error: @error.', array('@url' => $uri, '@error' => $e->getMessage()));
265 266 267 268 269
  }
  return FALSE;
}

/**
270
 * Downloads a translation file from a remote server.
271 272 273 274 275 276 277 278
 *
 * @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.
279 280 281
 * @param string $directory
 *   Directory where the downloaded file will be saved. Defaults to the
 *   temporary file path.
282 283 284
 *
 * @return object
 *   File object if download was successful. FALSE on failure.
285
 */
286 287 288 289
function locale_translation_download_source($source_file, $directory = 'temporary://') {
  if ($uri = system_retrieve_file($source_file->uri, $directory)) {
    $file = clone($source_file);
    $file->type = LOCALE_TRANSLATION_LOCAL;
290
    $file->uri = $uri;
291 292
    $file->directory = $directory;
    $file->timestamp = filemtime($uri);
293
    return $file;
294
  }
295
  watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source_file->uri), WATCHDOG_ERROR);
296
  return FALSE;
297
}