diff --git a/core/includes/config.inc b/core/includes/config.inc index 8d0eea1..b631a40 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -2,6 +2,7 @@ use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\ConfigException; /** * @file @@ -86,3 +87,30 @@ function config_get_storage_names_with_prefix($prefix = '') { function config($name, $class = 'Drupal\Core\Config\DrupalConfig') { return new $class(new DatabaseStorage($name)); } + +/** + * Wrapper for choosing the right translation from a config data array. + * + * Picks the language value requested or the interface language value if + * available or falls back to English if available. + */ +function config_get_by_langcode($data, $langcode = '') { + if (!$langcode) { + $langcode = drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode; + } + if (isset($data[$langcode])) { + return $data[$langcode]; + } + elseif (isset($data['_default']['langcode'])) { + if (!isset($data[$data['_default']['langcode']])) { + throw new ConfigException('Invalid default set.'); + } + return $data[$data['_default']['langcode']]; + } + else { + if (!isset($data['en'])) { + throw new ConfigException('Invalid default set.'); + } + return $data['en']; + } +} diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/DrupalConfig.php index f5a9220..76da10c 100644 --- a/core/lib/Drupal/Core/Config/DrupalConfig.php +++ b/core/lib/Drupal/Core/Config/DrupalConfig.php @@ -62,6 +62,13 @@ class DrupalConfig { /** * Gets data from this config object. * + * The configuration system does not retain data types. Every saved value is + * casted to a string. In most cases this is not an issue; however, it can + * cause issues with Booleans, which are casted to "1" (TRUE) or "0" (FALSE). + * In particular, code relying on === or !== will no longer function properly. + * + * @see http://php.net/manual/language.operators.comparison.php. + * * @param $key * A string that maps to a key within the configuration data. * For instance in the following configuation array: @@ -75,18 +82,17 @@ class DrupalConfig { * A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo' * would return array('bar' => 'baz'). * If no key is specified, then the entire data array is returned. - * - * The configuration system does not retain data types. Every saved value is - * casted to a string. In most cases this is not an issue; however, it can - * cause issues with Booleans, which are casted to "1" (TRUE) or "0" (FALSE). - * In particular, code relying on === or !== will no longer function properly. - * - * @see http://php.net/manual/language.operators.comparison.php. + * @param $subkey + * Typically a language code. The meaning of this is determined by the $type + * parameter. + * @param $type + * Same as was used for set(). Determines the wrapper function if a $subkey + * was used for set(). * * @return * The data that was requested. */ - public function get($key = '') { + public function get($key = '', $subkey = '', $type = 'langcode') { global $conf; $name = $this->storage->getName(); @@ -103,14 +109,22 @@ class DrupalConfig { else { $parts = explode('.', $key); if (count($parts) == 1) { - return isset($merged_data[$key]) ? $merged_data[$key] : NULL; + $return = isset($merged_data[$key]) ? $merged_data[$key] : NULL; } else { $key_exists = NULL; $value = drupal_array_get_nested_value($merged_data, $parts, $key_exists); - return $key_exists ? $value : NULL; + $return = $key_exists ? $value : NULL; } } + if (is_array($return) && ($subkey || ($subkey === '' && isset($return['_default'])))) { + $function = 'config_get_by_' . $type; + if (!function_exists($function)) { + throw new InvalidTypeException('Invalid type "'. $type . '" requested.'); + } + return $function($return, $subkey); + } + return $return; } /** @@ -131,14 +145,29 @@ class DrupalConfig { * @todo * @param $value * @todo + * @param $subkey + * Typically a langcode but can be anything. If the config data is new, + * this will be saved as _default. + * @param $type + * If a $subkey is used for the save, this will define the wrapper + * function called by get() to handle fallback. Defaults to language and + * rarely needs to be changed. */ - public function set($key, $value) { + public function set($key, $value, $subkey = '', $type = 'langcode') { // Type-cast value into a string. $value = $this->castValue($value); // The dot/period is a reserved character; it may appear between keys, but // not within keys. $parts = explode('.', $key); + if ($subkey) { + $default = $parts; + $default[] = '_default'; + if (!drupal_array_get_nested_value($this->data, $default)) { + drupal_array_set_nested_value($this->data, $default, array($type => $subkey)); + } + $parts[] = $subkey; + } if (count($parts) == 1) { $this->data[$key] = $value; } diff --git a/core/lib/Drupal/Core/Config/InvalidTypeException.php b/core/lib/Drupal/Core/Config/InvalidTypeException.php new file mode 100644 index 0000000..662c94f --- /dev/null +++ b/core/lib/Drupal/Core/Config/InvalidTypeException.php @@ -0,0 +1,8 @@ + 'Translation', + 'description' => 'Tests translation of configuration.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp('language'); + + // Set up a new container to ensure we are building a new Language object + // for each test. + drupal_container(new ContainerBuilder()); + } + + function testConfigTranslation() { + $config = config('config.test'); + $config->set('testkey', 'spanish value', 'es'); + $config->set('testkey', 'french value', 'fr'); + $config->set('testkey', 'english value', 'en'); + // Without the language system, the default is english. + $this->assertIdentical($config->get('testkey'), 'english value'); + // This falls back to french because there is no german translation and + // es was the first translation so it is the default. + $this->assertIdentical($config->get('testkey', 'de'), 'spanish value'); + // Set the default language to french. + $new_language_default = (object) array( + 'langcode' => 'fr', + 'default' => TRUE, + ); + language_save($new_language_default); + drupal_language_initialize(); + $this->assertIdentical($config->get('testkey'), 'french value'); + // Now verify the whole data. + $all_translations = $config->get('testkey', FALSE); + $this->assertIdentical($all_translations['_default']['langcode'], 'es'); + $this->assertIdentical($all_translations['es'], 'spanish value'); + $this->assertIdentical($all_translations['fr'], 'french value'); + $this->assertIdentical($all_translations['en'], 'english value'); + // Let's corrupt the data by removing the default and setting it back. + // Normally we use set on translatable data with a subkey like above. + unset($all_translations['_default']); + $config->set('testkey', $all_translations); + $config->save(); + // Without a german translation and with the default now gone, we will + // get the english version. + $this->assertIdentical($config->get('testkey', 'de'), 'english value'); + // With _default gone, there is no way for the system to recognize the + // translateability of the config. + $this->assertTrue(is_array($config->get('testkey'))); + } +}