diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 5f1adad..0bbc549 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -693,6 +693,12 @@ function install_tasks($install_state) { 'type' => 'batch', 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, ), + 'install_update_configuration_translations' => array( + 'display_name' => st('Translate configuration'), + 'display' => $needs_translations, + 'type' => 'batch', + 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, + ), 'install_finished' => array( 'display_name' => st('Finished'), ), @@ -1666,7 +1672,21 @@ function install_configure_form($form, &$form_state, &$install_state) { */ function install_import_translations_remaining(&$install_state) { include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; - return locale_translate_batch_import_update(array('langcode' => $install_state['parameters']['langcode'])); + return locale_translate_batch_import_files(array('langcode' => $install_state['parameters']['langcode'])); +} + +/** + * Creates configuration translations. + * + * @param $install_state + * An array of information about the current installation state. + * + * @return + * The batch definition, if there are configuration objects to update. + */ +function install_update_configuration_translations(&$install_state) { + include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; + return locale_config_batch_update_components(array('langcode' => $install_state['parameters']['langcode'])); } /** diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php index f232f63..efd7ce6 100644 --- a/core/lib/Drupal/Core/Config/InstallStorage.php +++ b/core/lib/Drupal/Core/Config/InstallStorage.php @@ -15,6 +15,13 @@ class InstallStorage extends FileStorage { /** + * Folder map indexed by configuration name + * + * @var array + */ + protected $folders; + + /** * Overrides Drupal\Core\Config\FileStorage::__construct(). */ public function __construct() { @@ -38,17 +45,9 @@ public function __construct() { * afterwards check for a corresponding module or theme. */ public function getFilePath($name) { - // Extract the owner. - $owner = strtok($name, '.'); - // Determine the path to the owner. - $path = FALSE; - foreach (array('profile', 'module', 'theme') as $type) { - if ($path = drupal_get_path($type, $owner)) { - $file = $path . '/config/' . $name . '.' . self::getFileExtension(); - if (file_exists($file)) { - return $file; - } - } + $folders = $this->getAllFolders(); + if (isset($folders[$name])) { + return $folders[$name] . '/' . $name . '.' . $this->getFileExtension(); } // If any code in the early installer requests a configuration object that // does not exist anywhere as default config, then that must be mistake. @@ -90,6 +89,74 @@ public function rename($name, $new_name) { * @throws Drupal\Core\Config\StorageException */ public function listAll($prefix = '') { - throw new StorageException('List operation is not allowed during install.'); + $names = array_keys($this->getAllFolders()); + if (!$prefix) { + return $names; + } + else { + foreach ($names as $index => $name) { + if (strpos($name, $prefix) !== 0 ) { + unset($names[$index]); + } + } + return $names; + } + } + + /** + * Returns a map of all metadata names and the module they belong to. + * + * @return array + * An array mapping metadata names with directories. + */ + protected function getAllFolders() { + if (!isset($this->folders)) { + $this->folders = $this->getComponentNames('profile', array(drupal_get_profile())); + $this->folders += $this->getComponentNames('module', module_list()); + $this->folders += $this->getComponentNames('theme', array_keys(list_themes())); + } + return $this->folders; + } + + /** + * Get all configuration names and folders for a list of modules or themes. + * + * @param string $type + * Type of components: 'module' | 'theme' | 'profile' + * @param array $list + * Array of theme or module names. + * + * @return array + * Folders indexed by configuration name. + */ + public function getComponentNames($type, $list) { + $extension = '.' . $this->getFileExtension(); + $folders = array(); + foreach ($list as $name) { + $directory = $this->getComponentFolder($type, $name); + if (file_exists($directory)) { + $files = glob($directory . '/*' . $extension); + foreach ($files as $filename) { + $name = basename($filename, $extension); + $folders[$name] = $directory; + } + } + } + return $folders; + } + + /** + * Get folder inside each component that contains the files. + * + * @param string $type + * Component type: 'module' | 'theme' | 'profile' + * @param string $name + * Component name. + * + * @return string + * The configuration folder name for this component. + */ + protected function getComponentFolder($type, $name) { + return drupal_get_path($type, $name) . '/config'; } } diff --git a/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php b/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php index 0776f18..5f16f41 100644 --- a/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php +++ b/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php @@ -7,84 +7,25 @@ namespace Drupal\Core\Config\Metadata; -use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\InstallStorage; /** * Defines the file storage controller for metadata files. */ -class MetadataStorage extends FileStorage { - - /** - * Metadata map indexing configuration names and modules. - * - * @var array - */ - protected $metaMap; - - /** - * Overrides Drupal\Core\Config\FileStorage::__construct(). - */ - public function __construct() { - // Do nothing. - } +class MetadataStorage extends InstallStorage { /** * Implements Drupal\Core\Config\StorageInterface::exists(). */ public function exists($name) { - return array_key_exists($name, $this->getMetadataMap()); - } - - /** - * Overrides Drupal\Core\Config\FileStorage::getFilePath(). - */ - public function getFilePath($name) { - $metamap = $this->getMetadataMap(); - $module = $metamap[$name]; - // We are looking for $module/meta/$name.yml - $directory = drupal_get_path('module', $module) . '/meta'; - return $directory . '/' . $name . '.yml'; - } - - /** - * Implements Drupal\Core\Config\StorageInterface::listAll(). - */ - public function listAll($prefix = '') { - $names = array_keys($this->getMetadataMap()); - if (!$prefix) { - return $names; - } - else { - foreach ($names as $index => $name) { - if (strpos($name, $prefix) !== 0 ) { - unset($names[$index]); - } - } - return $names; - } + return array_key_exists($name, $this->getAllFolders()); } /** - * Returns a map of all metadata names and the module they belong to. - * - * @return array - * An array mapping metadata names with module names. + * Overrides Drupal\Core\Config\InstallStorage::getComponentFolder(). */ - protected function getMetadataMap() { - if (!isset($this->metaMap)) { - $extension = '.' . self::getFileExtension(); - foreach (module_list() as $module) { - $directory = drupal_get_path('module', $module) . '/meta'; - if (file_exists($directory)) { - $files = glob($directory . '/*' . $extension); - foreach ($files as $filename) { - $name = basename($filename, $extension); - $this->metaMap[$name] = $module; - } - } - } - } - return $this->metaMap; + protected function getComponentFolder($type, $name) { + return drupal_get_path($type, $name) . '/meta'; } } diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 3bab052..8e01943 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -11,9 +11,8 @@ use Drupal\locale\Gettext; use Drupal\locale\PoDatabaseReader; use Drupal\Core\Language\Language; -use Drupal\Core\Config\Config; -use Drupal\Core\Config\FileStorage; -use Drupal\Core\Config\NullStorage; +use Drupal\Core\Config\InstallStorage; +use Drupal\Core\Config\StorageException; /** @@ -292,7 +291,11 @@ function locale_translate_add_language_set_batch($options) { ); // See if we have language files to import for the newly added language, // collect and import them. - if ($batch = locale_translate_batch_import_update($options)) { + if ($batch = locale_translate_batch_import_files($options)) { + batch_set($batch); + } + // Create or update all configuration translations for this language. + if ($batch = locale_config_batch_update_components($options)) { batch_set($batch); } } @@ -357,33 +360,6 @@ function locale_translate_batch_import_files($options, $force = FALSE) { } /** - * Prepare a batch to import translations and update configuration. - * - * @param array $options - * An array with options that can have the following elements: - * - 'components', Array with lists of components to update indexed by type. - * Defaults to all modules and themes. - * - All the options in locale_translate_batch_import_files(). - * @param $force - * (optional) Import all available files, even if they were imported before. - */ -function locale_translate_batch_import_update($options, $force = FALSE) { - $options += array('components' => array()); - $batch = locale_translate_batch_import_files($options, $force); - $langcodes = !empty($options['langcode']) ? array($options['langcode']) : array(); - if ($update_batch = locale_config_batch_update_components($options['components'], $langcodes)) { - if ($batch) { - // Merge both batches so we can see a result page for both. - $batch['operations'] = array_merge($batch['operations'], $update_batch['operations']); - } - else { - $batch = $update_batch; - } - } - return $batch; -} - -/** * Get an array of available interface translation file. * * @param $langcode @@ -621,14 +597,9 @@ function locale_translate_batch_finished($success, $results) { $skipped_files[] = $filepath; } } - $configuration = isset($results['stats']['configuration']) ? $results['stats']['configuration'] : 0; } drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes))); watchdog('locale', 'The translation was succesfully imported. %number new strings added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)); - if ($configuration) { - drupal_set_message(t('The configuration was successfully updated. There are %number configuration objects updated.', array('%number' => $configuration))); - watchdog('locale', 'The configuration was successfully updated. %number configuration objects updated.', array('%number' => $configuration)); - } if ($skips) { if (module_exists('dblog')) { $skip_message = format_plural($skips, 'A 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.', array('@url' => url('admin/reports/dblog'))); @@ -639,6 +610,10 @@ function locale_translate_batch_finished($success, $results) { drupal_set_message($skip_message, 'error'); watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING); } + // Merge feedback about configuration updates too. + if (isset($results['stats']['configuration'])) { + locale_config_batch_finished($success, $results); + } } } @@ -711,22 +686,37 @@ function locale_translate_delete_translation_files($langcode) { /** * Build a locale batch to refresh configuration. * + * @param array $options + * An array with options that can have the following elements: + * - 'langcode': The language code. Optional, defaults to NULL, which means + * that all translatable languages will be refreshed. + * - 'finish_feedback': Whether or not to give feedback to the user when the + * batch is finished. Optional, defaults to TRUE. * @param array $components - * Array of component lists indexed by type. - * @param array $langcodes - * (optional) Array of language codes to refresh. Defaults to all languages. + * (optional) Array of component lists indexed by type. If not present or it + * is an empty array, it will update all components. */ -function locale_config_batch_update_components($components, $langcodes = array()) { - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); +function locale_config_batch_update_components($options, $components = array()) { + $langcodes = isset($options['langcode']) ? array($options['langcode']) : array_keys(locale_translatable_language_list()); if ($langcodes && $names = _locale_config_component_names($components)) { - return locale_config_batch_build($names, $langcodes); + return locale_config_batch_build($names, $langcodes, $options); } } /** - * Build a locale batch to refresh specific configuration. + * Creates a locale batch to refresh specific configuration. + * + * @param array $names + * List of configuration object names to update. + * @param array $langcodes + * List of language codes to refresh. + * @param array $options + * 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. */ -function locale_config_batch_build($names, $langcodes) { +function locale_config_batch_build($names, $langcodes, $options = array()) { + $options += array('finish_feedback' => TRUE); $t = get_t(); foreach ($names as $name) { $operations[] = array('locale_config_batch_refresh_name', array($name, $langcodes)); @@ -738,6 +728,9 @@ function locale_config_batch_build($names, $langcodes) { 'error_message' => $t('Error updating configuration translations'), 'file' => drupal_get_path('module', 'locale') . '/locale.bulk.inc', ); + if (!empty($options['finish_feedback'])) { + $batch['completed'] = 'locale_config_batch_finished'; + } return $batch; } @@ -758,6 +751,23 @@ function locale_config_batch_refresh_name($name, $langcodes, &$context) { } /** + * Finished callback of system page locale import batch. + */ +function locale_config_batch_finished($success, $results) { + if ($success) { + $configuration = isset($results['stats']['configuration']) ? $results['stats']['configuration'] : 0; + if ($configuration) { + drupal_set_message(t('The configuration was successfully updated. There are %number configuration objects updated.', array('%number' => $configuration))); + watchdog('locale', 'The configuration was successfully updated. %number configuration objects updated.', array('%number' => $configuration)); + } + else { + drupal_set_message(t('No configuration objects have been updated.')); + watchdog('locale', 'No configuration objects have been updated.', array(), WATCHDOG_WARNING); + } + } +} + +/** * Delete configuration for language. * * @param $langcode @@ -782,69 +792,36 @@ function locale_config_delete_language($langcode) { * Number of configuration objects retranslated. */ function locale_config_update_multiple($names, $langcodes = array()) { + $default_storage = new InstallStorage(); + $config_storage = drupal_container()->get('config.storage'); $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); $count = 0; foreach ($names as $name) { - if ($data = locale_config_default_data($name)) { - $wrapper = new LocaleConfigWrapper($name, $data, locale_storage()); - foreach ($langcodes as $lang) { - $translation = $wrapper->getTranslation($lang, TRUE)->getData(); - locale_config_update_storage($name, $lang, $translation); - $count++; + $wrapper = NULL; + try { + if ($data = $default_storage->read($name)) { + $updated = $config_storage->read($name); + if ($data = _locale_config_data_compare($data, $updated)) { + $wrapper = new LocaleConfigWrapper($name, $data, locale_storage()); + } } - } - } - return $count; -} - -/** - * Update translated configuration. - * - * @param string $name - * Configuration name. - * @param string $langcode - * Language code. - * @param array $data - * Array with configuration data. - */ -function locale_config_update_storage($name, $langcode, $data) { - $locale_name = 'locale.config.' . $langcode . '.' . $name; - $storage = drupal_container()->get('config.storage'); - // Keep existing translations and merge new ones on top of them. - $current = $storage->read($locale_name); - $updated = $current ? NestedArray::mergeDeep($current, $data) : $data; - if ($updated) { - $storage->write($locale_name, $updated); - } - else { - $storage->delete($locale_name); - } - return $updated; -} - -/** - * Get default data for configuration object. - * - * @param string $name - * Configuration name. - */ -function locale_config_default_data($name) { - $data = &drupal_static(__FUNCTION__); - if (!isset($data[$name])) { - if ($directory = locale_config_default_folder($name)) { - $storage = new FileStorage($directory); - $data[$name] = $storage->read($name); - // Get default configuration and compare it with current one. - if ($data[$name]) { - $updated = drupal_container()->get('config.storage')->read($name); - $data[$name] = _locale_config_data_compare($data[$name], $updated); + foreach ($langcodes as $langcode) { + $locale_name = 'locale.config.' . $langcode . '.' . $name; + $translation = $wrapper ? $wrapper->getTranslation($langcode, TRUE)->getData() : NULL; + if ($translation) { + $config_storage->write($locale_name, $translation); + $count++; + } + else { + $config_storage->delete($locale_name); + } } } - else { - $data[$name] = array(); + catch (StorageException $e) { + // The configuration name does not exist, just continue. } } - return $data[$name]; + return $count; } /** @@ -877,30 +854,6 @@ function _locale_config_data_compare($default, $updated) { } /** - * Get all default configuration names and the path on which they are. - * - * @param string $name - * (optional) Configuration name to get the directory where it is. - * - * @return array|string - * All directories indexed by configuration name or only the one - * where the configuration name is if $name present. - */ -function locale_config_default_folder($name = NULL) { - $folders = &drupal_static(__FUNCTION__); - if (!$folders) { - $folders = _locale_config_default_folder('module', module_list()); - $folders += _locale_config_default_folder('theme', array_keys(list_themes())); - } - if ($name) { - return isset($folders[$name]) ? $folders[$name] : NULL; - } - else { - return $folders; - } -} - -/** * Delete configuration for uninstalled components. */ function locale_config_delete_components($components) { @@ -916,32 +869,6 @@ function locale_config_delete_components($components) { } } - -/** - * Get all configuration names and folders for a list of modules or themes. - * - * @param string $type - * Type of components: 'module' | 'theme' - * @param array $list - * Array of theme or module names. - * - * @return array - * Folders indexed by configuration name. - */ -function _locale_config_default_folder($type, $list) { - $folders = array(); - foreach ($list as $name) { - $directory = drupal_get_path($type, $name) . '/config'; - if (file_exists($directory)) { - $storage = new FileStorage($directory); - foreach ($storage->listAll() as $config_name) { - $folders[$config_name] = $directory; - } - } - } - return $folders; -} - /** * Get configuration names associated with components. * @@ -952,15 +879,16 @@ function _locale_config_default_folder($type, $list) { * Array of configuration object names. */ function _locale_config_component_names($components) { + $storage = new InstallStorage(); if ($components) { $names = array(); foreach ($components as $type => $list) { - $names = array_merge($names, array_keys(_locale_config_default_folder($type, $list))); + $names = array_merge($names, $storage->getComponentNames($type, $list)); } return $names; } else { - return array_keys(locale_config_default_folder()); + return $storage->listAll(); } } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 01ef845..b4771a0 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -466,8 +466,10 @@ function locale_system_update($components) { // built-in support for translation imports in the installer. if (!drupal_installation_attempted()) { include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; - $options['components'] = $components; - if ($batch = locale_translate_batch_import_update($options, TRUE)) { + if ($batch = locale_translate_batch_import_files(array(), TRUE)) { + batch_set($batch); + } + if ($batch = locale_config_batch_update_components(array(), $components)) { batch_set($batch); } } @@ -793,28 +795,40 @@ function locale_string_is_safe($string) { * Language codes for updated translations. * @param array $lids * List of string identifiers that have been updated / created. - * @param boolean $refresh_config - * (optional)Set to TRUE to refresh configuration translations too. */ -function _locale_refresh_translations($langcodes, $lids, $refresh_config = FALSE) { +function _locale_refresh_translations($langcodes, $lids) { if ($lids && $langcodes) { // Update javascript translations if any of the strings has a javascript location. if ($strings = locale_storage()->getStrings(array('lid' => $lids, 'type' => 'javascript'))) { array_map('_locale_invalidate_js', $langcodes); } - // Update configuration if any of the strings has a configuration location. - if ($refresh_config) { - include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; - if ($names = _locale_config_string_names($lids)) { - locale_config_update_multiple($names, $langcodes); - } - } } // Clear locale cache. cache()->invalidateTags(array('locale' => TRUE)); } /** + * Refresh configuration after string translations have been updated. + * + * The information that will be refreshed includes: + * - JavaScript translations. + * - Locale cache. + * + * @param array $langcodes + * Language codes for updated translations. + * @param array $lids + * List of string identifiers that have been updated / created. + */ +function _locale_refresh_configuration($langcodes, $lids) { + if ($lids && $langcodes) { + include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; + if ($names = _locale_config_string_names($lids)) { + locale_config_update_multiple($names, $langcodes); + } + } +} + +/** * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and * Drupal.formatPlural() and inserts them into the database. * diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index e6f2d4c..7ca6e17 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -438,8 +438,9 @@ function locale_translate_edit_form_submit($form, &$form_state) { } if ($updated) { - // Clear cache and force refresh of JavaScript translations. - _locale_refresh_translations(array($langcode), $updated, TRUE); + // Clear cache and refresh configuration and JavaScript translations. + _locale_refresh_configuration(array($langcode), $updated); + _locale_refresh_translations(array($langcode), $updated); } }