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

/**
 * @file
 * Mass import-export and batch import functionality for Gettext .po files.
 */

8
use Drupal\Core\Language\LanguageInterface;
9
use Drupal\file\FileInterface;
10 11
use Drupal\locale\Gettext;
use Drupal\locale\Locale;
12 13

/**
14
 * Prepare a batch to import all translations.
15
 *
16 17 18 19 20 21 22 23 24 25 26 27
 * @param array $options
 *   An array with options that can have the following elements:
 *   - 'langcode': The language code. Optional, defaults to NULL, which means
 *     that the language will be detected from the name of the files.
 *   - 'overwrite_options': Overwrite options array as defined in
 *     Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
 *   - 'customized': Flag indicating whether the strings imported from $file
 *     are customized translations or come from a community source. Use
 *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
 *     LOCALE_NOT_CUSTOMIZED.
 *   - 'finish_feedback': Whether or not to give feedback to the user when the
 *     batch is finished. Optional, defaults to TRUE.
28
 * @param bool $force
29
 *   (optional) Import all available files, even if they were imported before.
30
 *
31 32 33
 * @return array|bool
 *   The batch structure, or FALSE if no files are used to build the batch.
 *
34 35 36 37
 * @todo
 *   Integrate with update status to identify projects needed and integrate
 *   l10n_update functionality to feed in translation files alike.
 *   See http://drupal.org/node/1191488.
38
 */
39
function locale_translate_batch_import_files(array $options, $force = FALSE) {
40 41 42 43 44
  $options += array(
    'overwrite_options' => array(),
    'customized' => LOCALE_NOT_CUSTOMIZED,
    'finish_feedback' => TRUE,
  );
45

46 47
  if (!empty($options['langcode'])) {
    $langcodes = array($options['langcode']);
48 49 50
  }
  else {
    // If langcode was not provided, make sure to only import files for the
51
    // languages we have added.
52
    $langcodes = array_keys(\Drupal::languageManager()->getLanguages());
53
  }
54 55 56

  $files = locale_translate_get_interface_translation_files(array(), $langcodes);

57 58 59 60 61 62 63 64
  if (!$force) {
    $result = db_select('locale_file', 'lf')
      ->fields('lf', array('langcode', 'uri', 'timestamp'))
      ->condition('langcode', $langcodes)
      ->execute()
      ->fetchAllAssoc('uri');
    foreach ($result as $uri => $info) {
      if (isset($files[$uri]) && filemtime($uri) <= $info->timestamp) {
65
        // The file is already imported and not changed since the last import.
66 67 68 69
        // Remove it from file list and don't import it again.
        unset($files[$uri]);
      }
    }
70
  }
71
  return locale_translate_batch_build($files, $options);
72 73
}

74
/**
75
 * Get interface translation files present in the translations directory.
76
 *
77
 * @param array $projects
78 79
 *   (optional) Project names from which to get the translation files and
 *   history. Defaults to all projects.
80
 * @param array $langcodes
81 82
 *   (optional) Language codes from which to get the translation files and
 *   history. Defaults to all languages.
83 84
 *
 * @return array
85
 *   An array of interface translation files keyed by their URI.
86
 */
87
function locale_translate_get_interface_translation_files(array $projects = array(), array $langcodes = array()) {
88 89 90 91 92 93 94 95 96
  module_load_include('compare.inc', 'locale');
  $files = array();
  $projects = $projects ? $projects : array_keys(locale_translation_get_projects());
  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());

  // Scan the translations directory for files matching a name pattern
  // containing a project name and language code: {project}.{langcode}.po or
  // {project}-{version}.{langcode}.po.
  // Only files of known projects and languages will be returned.
97
  $directory = \Drupal::config('locale.settings')->get('translation.path');
98
  $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', array('recurse' => FALSE));
99

100 101 102 103 104 105
  foreach ($result as $file) {
    // Update the file object with project name and version from the file name.
    $file = locale_translate_file_attach_properties($file);
    if (in_array($file->project, $projects)) {
      if (in_array($file->langcode, $langcodes)) {
        $files[$file->uri] = $file;
106 107
      }
    }
108
  }
109

110
  return $files;
111 112
}

113 114 115
/**
 * Build a locale batch from an array of files.
 *
116
 * @param array $files
117
 *   Array of file objects to import.
118 119 120 121 122 123 124 125 126 127 128 129
 * @param array $options
 *   An array with options that can have the following elements:
 *   - 'langcode': The language code. Optional, defaults to NULL, which means
 *     that the language will be detected from the name of the files.
 *   - 'overwrite_options': Overwrite options array as defined in
 *     Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
 *   - 'customized': Flag indicating whether the strings imported from $file
 *     are customized translations or come from a community source. Use
 *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
 *     LOCALE_NOT_CUSTOMIZED.
 *   - 'finish_feedback': Whether or not to give feedback to the user when the
 *     batch is finished. Optional, defaults to TRUE.
130
 *
131
 * @return array|bool
132
 *   A batch structure or FALSE if $files was empty.
133
 */
134
function locale_translate_batch_build(array $files, array $options) {
135 136 137 138 139
  $options += array(
    'overwrite_options' => array(),
    'customized' => LOCALE_NOT_CUSTOMIZED,
    'finish_feedback' => TRUE,
  );
140 141 142
  if (count($files)) {
    $operations = array();
    foreach ($files as $file) {
143
      // We call locale_translate_batch_import for every batch operation.
144
      $operations[] = array('locale_translate_batch_import', array($file, $options));
145
    }
146
    // Save the translation status of all files.
147
    $operations[] = array('locale_translate_batch_import_save', array());
148

149 150 151
    // Add a final step to refresh JavaScript and configuration strings.
    $operations[] = array('locale_translate_batch_refresh', array());

152 153
    $batch = array(
      'operations'    => $operations,
154
      'title'         => t('Importing interface translations'),
155
      'progress_message' => '',
156
      'error_message' => t('Error importing interface translations'),
157 158
      'file'          => drupal_get_path('module', 'locale') . '/locale.bulk.inc',
    );
159
    if ($options['finish_feedback']) {
160
      $batch['finished'] = 'locale_translate_batch_finished';
161 162 163 164 165 166 167
    }
    return $batch;
  }
  return FALSE;
}

/**
168 169 170
 * Implements callback_batch_operation().
 *
 * Perform interface translation import.
171
 *
172 173
 * @param object $file
 *   A file object of the gettext file to be imported. The file object must
174 175 176
 *   contain a language parameter (other than
 *   LanguageInterface::LANGCODE_NOT_SPECIFIED). This is used as the language of
 *   the import.
177 178
 * @param array $options
 *   An array with options that can have the following elements:
179
 *   - 'langcode': The language code.
180 181 182 183 184 185
 *   - 'overwrite_options': Overwrite options array as defined in
 *     Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
 *   - 'customized': Flag indicating whether the strings imported from $file
 *     are customized translations or come from a community source. Use
 *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
 *     LOCALE_NOT_CUSTOMIZED.
186 187
 *   - 'message': Alternative message to display during import. Note, this must
 *     be sanitized text.
188
 * @param array $context
189 190
 *   Contains a list of files imported.
 */
191
function locale_translate_batch_import($file, array $options, array &$context) {
192 193 194 195 196
  // Merge the default values in the $options array.
  $options += array(
    'overwrite_options' => array(),
    'customized' => LOCALE_NOT_CUSTOMIZED,
  );
197

198
  if (isset($file->langcode) && $file->langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED) {
199

200
    try {
201 202
      if (empty($context['sandbox'])) {
        $context['sandbox']['parse_state'] = array(
203
          'filesize' => filesize(drupal_realpath($file->uri)),
204 205 206 207 208 209 210 211 212 213 214
          'chunk_size' => 200,
          'seek' => 0,
        );
      }
      // Update the seek and the number of items in the $options array().
      $options['seek'] = $context['sandbox']['parse_state']['seek'];
      $options['items'] = $context['sandbox']['parse_state']['chunk_size'];
      $report = GetText::fileToDatabase($file, $options);
      // If not yet finished with reading, mark progress based on size and
      // position.
      if ($report['seek'] < filesize($file->uri)) {
215

216 217 218 219 220 221
        $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,
        // because of floating point inaccuracies. See
        // http://drupal.org/node/1089472
        $context['finished'] = min(0.95, $report['seek'] / filesize($file->uri));
222
        if (isset($options['message'])) {
223
          $context['message'] = t('!message (@percent%).', array('!message' => $options['message'], '@percent' => (int) ($context['finished'] * 100)));
224 225 226 227
        }
        else {
          $context['message'] = t('Importing translation file: %filename (@percent%).', array('%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)));
        }
228 229 230 231
      }
      else {
        // We are finished here.
        $context['finished'] = 1;
232 233

        // Store the file data for processing by the next batch operation.
234
        $file->timestamp = filemtime($file->uri);
235 236
        $context['results']['files'][$file->uri] = $file;
        $context['results']['languages'][$file->uri] = $file->langcode;
237
      }
238 239 240 241 242 243

      // Add the reported values to the statistics for this file.
      // Each import iteration reports statistics in an array. The results of
      // each iteration are added and merged here and stored per file.
      if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$file->uri])) {
        $context['results']['stats'][$file->uri] = array();
244 245
      }
      foreach ($report as $key => $value) {
246 247 248 249 250
        if (is_numeric($report[$key])) {
          if (!isset($context['results']['stats'][$file->uri][$key])) {
            $context['results']['stats'][$file->uri][$key] = 0;
          }
          $context['results']['stats'][$file->uri][$key] += $report[$key];
251 252
        }
        elseif (is_array($value)) {
253 254
          $context['results']['stats'][$file->uri] += array($key => array());
          $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
255 256
        }
      }
257 258
    }
    catch (Exception $exception) {
259 260
      // Import failed. Store the data of the failing file.
      $context['results']['failed_files'][] = $file;
261
      \Drupal::logger('locale')->notice('Unable to import translations file: @file', array('@file' => $file->uri));
262 263 264 265 266
    }
  }
}

/**
267 268 269
 * Implements callback_batch_operation().
 *
 * Save data of imported files.
270
 *
271
 * @param array $context
272 273
 *   Contains a list of imported files.
 */
274
function locale_translate_batch_import_save(array $context) {
275 276 277 278 279 280 281 282 283 284 285
  if (isset($context['results']['files'])) {
    foreach ($context['results']['files'] as $file) {
      // Update the file history if both project and version are known. This
      // table is used by the automated translation update function which tracks
      // translation status of module and themes in the system. Other
      // translation files are not tracked and are therefore not stored in this
      // table.
      if ($file->project && $file->version) {
        $file->last_checked = REQUEST_TIME;
        locale_translation_update_file_history($file);
      }
286
    }
287
    $context['message'] = t('Translations imported.');
288 289 290
  }
}

291
/**
292 293
 * Implements callback_batch_operation().
 *
294 295 296 297 298 299 300 301 302 303 304
 * Refreshs translations after importing strings.
 *
 * @param array $context
 *   Contains a list of strings updated and information about the progress.
 */
function locale_translate_batch_refresh(array &$context) {
  if (!isset($context['sandbox']['refresh'])) {
    $strings = $langcodes = array();
    if (isset($context['results']['stats'])) {
      // Get list of unique string identifiers and language codes updated.
      $langcodes = array_unique(array_values($context['results']['languages']));
305
      foreach ($context['results']['stats'] as $report) {
306 307 308 309 310
        $strings = array_merge($strings, $report['strings']);
      }
    }
    if ($strings) {
      // Initialize multi-step string refresh.
311
      $context['message'] = t('Updating translations for JavaScript and default configuration.');
312 313 314 315 316 317 318
      $context['sandbox']['refresh']['strings'] = array_unique($strings);
      $context['sandbox']['refresh']['languages'] = $langcodes;
      $context['sandbox']['refresh']['names'] = array();
      $context['results']['stats']['config'] = 0;
      $context['sandbox']['refresh']['count'] = count($strings);

      // We will update strings on later steps.
319
      $context['finished'] = 0;
320 321 322 323 324 325 326 327 328
    }
    else {
      $context['finished'] = 1;
    }
  }
  elseif ($name = array_shift($context['sandbox']['refresh']['names'])) {
    // Refresh all languages for one object at a time.
    $count = locale_config_update_multiple(array($name), $context['sandbox']['refresh']['languages']);
    $context['results']['stats']['config'] += $count;
329 330 331 332
    // Inherit finished information from the "parent" string lookup step so
    // visual display of status will make sense.
    $context['finished'] = $context['sandbox']['refresh']['names_finished'];
    $context['message'] = t('Updating default configuration (@percent%).', array('@percent' => (int) ($context['finished'] * 100)));
333 334 335 336 337 338 339 340 341 342 343
  }
  elseif (!empty($context['sandbox']['refresh']['strings'])) {
    // Not perfect but will give some indication of progress.
    $context['finished'] = 1 - count($context['sandbox']['refresh']['strings']) / $context['sandbox']['refresh']['count'];
    // Pending strings, refresh 100 at a time, get next pack.
    $next = array_slice($context['sandbox']['refresh']['strings'], 0, 100);
    array_splice($context['sandbox']['refresh']['strings'], 0, count($next));
    // Clear cache and force refresh of JavaScript translations.
    _locale_refresh_translations($context['sandbox']['refresh']['languages'], $next);
    // Check whether we need to refresh configuration objects.
    if ($names = \Drupal\locale\Locale::config()->getStringNames($next)) {
344
      $context['sandbox']['refresh']['names_finished'] = $context['finished'];
345 346 347 348
      $context['sandbox']['refresh']['names'] = $names;
    }
  }
  else {
349
    $context['message'] = t('Updated default configuration.');
350 351 352 353
    $context['finished'] = 1;
  }
}

354
/**
355 356
 * Implements callback_batch_finished().
 *
357
 * Finished callback of system page locale import batch.
358 359 360 361 362
 *
 * @param bool $success
 *   TRUE if batch successfully completed.
 * @param array $results
 *   Batch results.
363
 */
364
function locale_translate_batch_finished($success, array $results) {
365
  $logger = \Drupal::logger('locale');
366
  if ($success) {
367
    $additions = $updates = $deletes = $skips = $config = 0;
368
    if (isset($results['failed_files'])) {
369
      if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) {
370
        $message = \Drupal::translation()->formatPlural(count($results['failed_files']), 'One translation file could not be imported. <a href="@url">See the log</a> for details.', '@count translation files could not be imported. <a href="@url">See the log</a> for details.', array('@url' => \Drupal::url('dblog.overview')));
371 372
      }
      else {
373
        $message = \Drupal::translation()->formatPlural(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.');
374 375
      }
      drupal_set_message($message, 'error');
376
    }
377 378 379 380 381 382 383 384 385 386 387 388 389 390
    if (isset($results['files'])) {
      $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;
          }
        }
391
      }
392
      drupal_set_message(\Drupal::translation()->formatPlural(count($results['files']),
393 394 395 396
        'One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
        '@count translation files imported. %number translations were added, %update translations were updated and %delete translations were removed.',
        array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)
      ));
397
      $logger->notice('Translations imported: %number added, %update updated, %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
398 399

      if ($skips) {
400
        if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) {
401
          $message = \Drupal::translation()->formatPlural($skips, 'One translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => \Drupal::url('dblog.overview')));
402 403
        }
        else {
404
          $message = \Drupal::translation()->formatPlural($skips, 'One 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.');
405 406
        }
        drupal_set_message($message, 'warning');
407
        $logger->warning('@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)));
408
      }
409
    }
410
  }
411 412 413 414
  // Add messages for configuration too.
  if (isset($results['stats']['config'])) {
    locale_config_batch_finished($success, $results);
  }
415
}
416 417 418 419

/**
 * Creates a file object and populates the timestamp property.
 *
420
 * @param string $filepath
421 422
 *   The filepath of a file to import.
 *
423
 * @return object
424 425 426 427 428 429
 *   An object representing the file.
 */
function locale_translate_file_create($filepath) {
  $file = new stdClass();
  $file->filename = drupal_basename($filepath);
  $file->uri = $filepath;
430
  $file->timestamp = filemtime($file->uri);
431 432 433 434
  return $file;
}

/**
435
 * Generates file properties from filename and options.
436
 *
437 438 439 440
 * An attempt is made to determine the translation language, project name and
 * project version from the file name. Supported file name patterns are:
 * {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po.
 * Alternatively the translation language can be set using the $options.
441
 *
442 443 444 445 446
 * @param object $file
 *   A file object of the gettext file to be imported.
 * @param array $options
 *   An array with options:
 *   - 'langcode': The language code. Overrides the file language.
447
 *
448 449
 * @return object
 *   Modified file object.
450
 */
451
function locale_translate_file_attach_properties($file, array $options = array()) {
452 453 454 455 456 457 458 459
  // If $file is a file entity, convert it to a stdClass.
  if ($file instanceof FileInterface) {
    $file = (object) array(
      'filename' => $file->getFilename(),
      'uri' => $file->getFileUri(),
    );
  }

460
  // Extract project, version and language code from the file name. Supported:
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
  // {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po
  preg_match('!
    (                       # project OR project and version OR emtpy (group 1)
      ([a-z_]+)             # project name (group 2)
      \.                    # .
      |                     # OR
      ([a-z_]+)             # project name (group 3)
      \-                    # -
      ([0-9a-z\.\-\+]+)     # version (group 4)
      \.                    # .
      |                     # OR
    )                       # (empty)
    ([^\./]+)               # language code (group 5)
    \.                      # .
    po                      # po extension
    $!x', $file->filename, $matches);
  if (isset($matches[5])) {
    $file->project = $matches[2] . $matches[3];
    $file->version = $matches[4];
    $file->langcode = isset($options['langcode']) ? $options['langcode'] : $matches[5];
481 482
  }
  else {
483
    $file->langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
484
  }
485
  return $file;
486 487 488
}

/**
489
 * Deletes interface translation files and translation history records.
490
 *
491
 * @param array $projects
492 493
 *   (optional) Project names from which to delete the translation files and
 *   history. Defaults to all projects.
494
 * @param array $langcodes
495 496
 *   (optional) Language codes from which to delete the translation files and
 *   history. Defaults to all languages.
497
 *
498
 * @return bool
499
 *   TRUE if files are removed successfully. FALSE if one or more files could
500
 *   not be deleted.
501
 */
502
function locale_translate_delete_translation_files(array $projects = array(), array $langcodes = array()) {
503 504 505 506 507
  $fail = FALSE;
  locale_translation_file_history_delete($projects, $langcodes);

  // Delete all translation files from the translations directory.
  if ($files = locale_translate_get_interface_translation_files($projects, $langcodes)) {
508 509 510
    foreach ($files as $file) {
      $success = file_unmanaged_delete($file->uri);
      if (!$success) {
511
        $fail = TRUE;
512 513 514
      }
    }
  }
515
  return !$fail;
516
}
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533

/**
 * Builds a locale batch to refresh configuration.
 *
 * @param array $options
 *   An array with options that can have the following elements:
 *   - 'finish_feedback': (optional) Whether or not to give feedback to the user
 *     when the batch is finished. Defaults to TRUE.
 * @param array $langcodes
 *   (optional) Array of language codes. Defaults to all translatable languages.
 * @param array $components
 *   (optional) Array of component lists indexed by type. If not present or it
 *   is an empty array, it will update all components.
 *
 * @return array
 *   The batch definition.
 */
534
function locale_config_batch_update_components(array $options, array $langcodes = array(), array $components = array()) {
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
  if ($langcodes && $names = \Drupal\locale\Locale::config()->getComponentNames($components)) {
    return locale_config_batch_build($names, $langcodes, $options);
  }
}

/**
 * Creates a locale batch to refresh specific configuration.
 *
 * @param array $names
 *   List of configuration object names (which are strings) to update.
 * @param array $langcodes
 *   List of language codes to refresh.
 * @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. Defaults to TRUE.
 *
 * @return array
 *   The batch definition.
 *
 * @see locale_config_batch_refresh_name()
 */
558
function locale_config_batch_build(array $names, array $langcodes, array $options = array()) {
559
  $options += array('finish_feedback' => TRUE);
560 561
  $i = 0;
  $batch_names = array();
562
  $operations = array();
563
  foreach ($names as $name) {
564 565
    $batch_names[] = $name;
    $i++;
566 567 568 569
    // During installation the caching of configuration objects is disabled so
    // it is very expensive to initialize the \Drupal::config() object on each
    // request. We batch a small number of configuration object upgrades
    // together to improve the overall performance of the process.
570 571 572 573 574 575 576
    if ($i % 20 == 0) {
      $operations[] = array('locale_config_batch_refresh_name', array($batch_names, $langcodes));
      $batch_names = array();
    }
  }
  if (!empty($batch_names)) {
    $operations[] = array('locale_config_batch_refresh_name', array($batch_names, $langcodes));
577 578 579
  }
  $batch = array(
    'operations'    => $operations,
580 581 582
    'title'         => t('Updating configuration translations'),
    'init_message'  => t('Starting configuration update'),
    'error_message' => t('Error updating configuration translations'),
583 584 585 586 587 588 589 590 591
    'file'          => drupal_get_path('module', 'locale') . '/locale.bulk.inc',
  );
  if (!empty($options['finish_feedback'])) {
    $batch['completed'] = 'locale_config_batch_finished';
  }
  return $batch;
}

/**
592 593 594
 * Implements callback_batch_operation().
 *
 * Performs configuration translation refresh.
595
 *
596 597
 * @param array $names
 *   An array of names of configuration objects to update.
598 599 600 601 602 603 604
 * @param array $langcodes
 *   (optional) Array of language codes to update. Defaults to all languages.
 * @param array $context
 *   Contains a list of files imported.
 *
 * @see locale_config_batch_build()
 */
605
function locale_config_batch_refresh_name(array $names, array $langcodes, array &$context) {
606 607 608
  if (!isset($context['result']['stats']['config'])) {
    $context['result']['stats']['config'] = 0;
  }
609 610 611 612
  $context['result']['stats']['config'] += locale_config_update_multiple($names, $langcodes);
  foreach ($names as $name) {
    $context['result']['names'][] = $name;
  }
613 614 615 616 617
  $context['result']['langcodes'] = $langcodes;
  $context['finished'] = 1;
}

/**
618 619
 * Implements callback_batch_finished().
 *
620 621 622 623 624 625
 * Finishes callback of system page locale import batch.
 *
 * @param bool $success
 *   Information about the success of the batch import.
 * @param array $results
 *   Information about the results of the batch import.
626 627
 *
 * @see locale_config_batch_build()
628 629 630 631 632 633
 */
function locale_config_batch_finished($success, array $results) {
  if ($success) {
    $configuration = isset($results['stats']['config']) ? $results['stats']['config'] : 0;
    if ($configuration) {
      drupal_set_message(t('The configuration was successfully updated. There are %number configuration objects updated.', array('%number' => $configuration)));
634
      \Drupal::logger('locale')->notice('The configuration was successfully updated. %number configuration objects updated.', array('%number' => $configuration));
635 636 637
    }
    else {
      drupal_set_message(t('No configuration objects have been updated.'));
638
      \Drupal::logger('locale')->warning('No configuration objects have been updated.');
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
    }
  }
}

/**
 * Updates all configuration for names / languages.
 *
 * @param array $names
 *   Array of names of configuration objects to update.
 * @param array $langcodes
 *   (optional) Array of language codes to update. Defaults to all languages.
 *
 * @return int
 *   Number of configuration objects retranslated.
 */
654
function locale_config_update_multiple(array $names, array $langcodes = array()) {
655 656 657 658
  /** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
  $language_manager = \Drupal::languageManager();
  $locale_config_manager = Locale::config();

659 660 661
  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
  $count = 0;
  foreach ($names as $name) {
662
    $wrapper = $locale_config_manager->get($name);
663 664 665
    foreach ($langcodes as $langcode) {
      $translation = $wrapper->getValue() ? $wrapper->getTranslation($langcode)->getValue() : NULL;
      if ($translation) {
666
        $locale_config_manager->saveTranslationData($name, $langcode, $translation);
667 668 669
        $count++;
      }
      else {
670 671 672 673 674
        // Do not bother deleting language overrides which do not exist in the
        // first place.
        if (!$language_manager->getLanguageConfigOverride($langcode, $name)->isNew()) {
          $locale_config_manager->deleteTranslationData($name, $langcode);
        }
675 676 677 678 679
      }
    }
  }
  return $count;
}