diff --git a/core/includes/update.inc b/core/includes/update.inc index b0d09a7..f711507 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -9,6 +9,7 @@ */ use Drupal\Component\Graph\Graph; +use Drupal\Core\Config\FileStorage; /** * Minimum schema version of Drupal 7 required for upgrade to Drupal 8. @@ -877,64 +878,66 @@ function update_retrieve_dependencies() { * Provide a generalised method to migrate variables from Drupal 7 to Drupal 8's * configuration management system. * - * @param $config_name - * The name of the configuration object to retrieve. The name corresponds to - * an XML configuration file. For example, passing "book.admin" will return - * the config object containing the contents of book.admin.xml. - * @param $variable_map - * An array to map new to old configuration naming conventions. Example: + * @param string $config_name + * The configuration object name to retrieve. + * @param array $variable_map + * An associative array that maps old variables names to new configuration + * object keys; e.g.: * @code - * array('new_config' => 'old_config') + * array('old_variable' => 'new_config.sub_key') * @endcode - * This would update the value for new_config to the value old_config has in - * the variable table. + * This would migrate the value contained in variable name 'old_variable' into + * the data key 'new_config.sub_key' of the configuration object $config_name. */ -function update_variables_to_config($config_name, array $variable_map = array()) { +function update_variables_to_config($config_name, array $variable_map) { + // Build the new configuration object. + // This potentially loads an existing configuration object, in case another + // update function migrated configuration values into $config_name already. $config = config($config_name); - $config_data = array_keys($config->get()); - - if (!empty($config_data)) { - // Build a list of variables to select from the database and build a mapping - // of variable names to config keys. - foreach ($config_data as $config_key) { - if (isset($variable_map[$config_key])) { - $variables[] = $variable_map[$config_key]; - $config_keys[$variable_map[$config_key]] = $config_key; - } - else { - $variables[] = $config_key; - $config_keys[$config_key] = $config_key; - } - } + $original_data = $config->get(); + + // Extract the module namespace/owner from the configuration object name. + $module = strtok($config_name, '.'); + + // Load and set default configuration values. + // Throws a FileStorageReadException if there is no default configuration + // file, which is required to exist. + $file = new FileStorage($config_name); + $file->setPath(drupal_get_path('module', $module) . '/config'); + $default_data = $file->read(); + + // Merge any possibly existing original data into default values. + // Only relevant when being called repetitively on the same config object. + if (!empty($original_data)) { + $data = drupal_array_merge_deep($default_data, $original_data); + } + else { + $data = $default_data; + } - // Get any variables currently defined that match the new setting names in - // the config file. - $query = db_select('variable', 'v') - ->fields('v') - ->condition('name', $variables, 'IN'); - - $var_values = $query->execute()->fetchAllKeyed(0); - if (!empty($var_values)) { - // Update the config system settings to use the values previously stored in - // the variable table. - try { - foreach($var_values as $name => $val) { - $config->set($config_keys[$name], unserialize($val)); - } - $config->save(); - // Delete the old variables. The config system will throw an exception if a - // value cannot be saved, so this code will not run if there is a problem - // running the update. - $del = db_delete('variable')->condition('name', $variables, 'IN'); - $del->execute(); - } - // @TODO We may want to do different error handling for different - // exception types, but for now we'll just log the exception. - catch (Exception $e) { - watchdog_exception('update', $e); - } + // Apply the default values. + $config->setData($data); + + // Fetch existing variables. + $variables = db_query('SELECT name, value FROM {variable} WHERE name IN (:variables)', array(':variables' => array_keys($variable_map)))->fetchAllKeyed(); + + // Set configuration values according to the provided variable mapping. + foreach ($variable_map as $variable_name => $config_key) { + // This function migrates variables regardless of their value, including + // NULL values. Any possibly required customizations need to be performed + // manually, either via variable_set() before calling this function or via + // config() after calling this function. + if (isset($variables[$variable_name])) { + $value = unserialize($variables[$variable_name]); + $config->set($config_key, $value); } } + + // Save the configuration object. + $config->save(); + + // Delete the migrated variables. + db_delete('variable')->condition('name', array_keys($variable_map), 'IN')->execute(); } /** diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 5e2ac1e..2a6d448 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -105,13 +105,13 @@ class FileStorage { */ public function read() { if (!$this->exists()) { - throw new FileStorageReadException('Configuration file does not exist.'); + throw new FileStorageReadException("Configuration file '$this->name' does not exist."); } $data = file_get_contents($this->getFilePath()); $data = $this->decode($data); if ($data === FALSE) { - throw new FileStorageReadException('Unable to decode configuration file.'); + throw new FileStorageReadException("Failed to decode configuration file '$this->name'."); } return $data; } diff --git a/core/modules/block/block.install b/core/modules/block/block.install index 632e106..7544c33 100644 --- a/core/modules/block/block.install +++ b/core/modules/block/block.install @@ -224,6 +224,8 @@ function block_install() { /** * Block cache is always enabled in 8.x. + * + * @ingroup config_upgrade */ function block_update_8000() { variable_del('block_cache'); diff --git a/core/modules/config/config.test b/core/modules/config/config.test index 257ab0f..f0e7df0 100644 --- a/core/modules/config/config.test +++ b/core/modules/config/config.test @@ -315,16 +315,15 @@ class ConfOverrideTestCase extends WebTestBase { } /** - * Tests function providing configuration upgrade from Drupal 7 to 8. + * Tests migration of variables into configuration objects. */ -class ConfUpdate7to8TestCase extends WebTestBase { +class ConfigUpgradeTestCase extends WebTestBase { protected $testContent = 'Olá, Sao Paulo!'; public static function getInfo() { return array( - 'name' => 'Configuration update from Drupal 7 to 8', - 'description' => 'Tests the ability to update Drupal 7 variables to the - configuration management system.', + 'name' => 'Variable migration', + 'description' => 'Tests migration of variables into configuration objects.', 'group' => 'Configuration', ); } @@ -335,18 +334,56 @@ class ConfUpdate7to8TestCase extends WebTestBase { } /** - * Test configuration update function. + * Tests update_variables_to_config(). */ function testConfigurationUpdate() { // Ensure that the variable table has the object. The variable table will // remain in place for Drupal 8 to provide an upgrade path for overridden // variables. - db_merge('variable')->key(array('name' => 'config_test_foo'))->fields(array('value' => serialize($this->testContent)))->execute(); - db_merge('variable')->key(array('name' => 'config_bar'))->fields(array('value' => serialize($this->testContent)))->execute(); - - update_variables_to_config('config.test', array('config_test_bar' => 'config_bar')); - $config = config('config.test'); - $this->assertEqual($config->get('config_test_foo'), $this->testContent); - $this->assertEqual($config->get('config_test_bar'), $this->testContent); + db_insert('variable') + ->fields(array('name', 'value')) + ->values(array('config_upgrade_foo', serialize($this->testContent))) + ->values(array('config_upgrade_bar', serialize($this->testContent))) + ->execute(); + + // Perform migration. + update_variables_to_config('config_upgrade.test', array( + 'config_upgrade_bar' => 'parent.bar', + 'config_upgrade_foo' => 'foo', + // A default configuration value for which no variable exists. + 'config_upgrade_baz' => 'parent.baz', + )); + + // Verify that variables have been converted and default values exist. + $config = config('config_upgrade.test'); + $this->assertIdentical($config->get('foo'), $this->testContent); + $this->assertIdentical($config->get('parent.bar'), $this->testContent); + $this->assertIdentical($config->get('parent.baz'), 'Baz'); + + // Verify that variables have been deleted. + $variables = db_query('SELECT name FROM {variable} WHERE name IN (:names)', array(':names' => array('config_upgrade_bar', 'config_upgrade_foo')))->fetchCol(); + $this->assertFalse($variables); + + // Add another variable to migrate into the same config object. + db_insert('variable') + ->fields(array('name', 'value')) + ->values(array('config_upgrade_additional', serialize($this->testContent))) + ->execute(); + + // Perform migration into the exsting config object. + update_variables_to_config('config_upgrade.test', array( + 'config_upgrade_additional' => 'parent.additional', + )); + + // Verify that new variables have been converted and existing still exist. + $config = config('config_upgrade.test'); + $this->assertIdentical($config->get('foo'), $this->testContent); + $this->assertIdentical($config->get('parent.bar'), $this->testContent); + $this->assertIdentical($config->get('parent.baz'), 'Baz'); + $this->assertIdentical($config->get('parent.additional'), $this->testContent); + + // Verify that variables have been deleted. + $variables = db_query('SELECT name FROM {variable} WHERE name IN (:names)', array(':names' => array('config_upgrade_additional')))->fetchCol(); + $this->assertFalse($variables); } } diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index b29bdee..ac03baa 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -157,6 +157,8 @@ function locale_update_8000() { /** * Language type 'language' renamed to 'language_interface'. + * + * @ingroup config_upgrade */ function locale_update_8001() { // Only change language_types if we had this setting saved. Keep order @@ -242,6 +244,8 @@ function locale_update_8002() { /** * Converts language domains to new format. + * + * @ingroup config_upgrade */ function locale_update_8003() { $message = ''; @@ -274,6 +278,8 @@ function locale_update_8003() { /** * Rename language providers to language negotiation methods. + * + * @ingroup config_upgrade */ function locale_update_8004() { $types = variable_get('language_types', NULL); @@ -437,6 +443,8 @@ function locale_update_8006() { /** * Convert language_negotiation_* variables to use the new callbacks. + * + * @ingroup config_upgrade */ function locale_update_8007() { $variable_names = array( @@ -510,6 +518,8 @@ function locale_update_8007() { /** * Rename the option variables of the locale language negotiation. + * + * @ingroup config_upgrade */ function locale_update_8008() { $variable_name_map = array( diff --git a/core/modules/node/node.install b/core/modules/node/node.install index ee89059..6f806e9 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -511,26 +511,11 @@ function _update_7000_node_get_types() { */ /** - * Set 'node' as front page path if it implicitly was before. - * - * Node module became optional. The default front page path was changed to - * 'user'. Since 'node' was the implicit default front page path previously and - * may not have been explicitly configured as such, this update ensures that the - * old implicit default is still the default. - * - * @see http://drupal.org/node/375397 - */ -function node_update_8000() { - $front_page = variable_get('site_frontpage'); - if (!isset($front_page)) { - variable_set('site_frontpage', 'node'); - } -} - -/** * Rename node type language variable names. * * @see http://drupal.org/node/540294 + * + * @ingroup config_upgrade */ function node_update_8001() { $types = db_query('SELECT type FROM {node_type}')->fetchCol(); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 5581d01..84822c7 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1688,6 +1688,17 @@ function system_update_last_removed() { } /** + * @defgroup config_upgrade Configuration system upgrade functions + * @{ + * Module update functions that + * - update variables prior to configuration system conversions + * - convert variables to the new configuration system + * - update configuration system values after conversion + * + * @} End of "defgroup config_upgrade". + */ + +/** * @defgroup updates-7.x-to-8.x Updates from 7.x to 8.x * @{ * Update functions from 7.x to 8.x. @@ -1713,7 +1724,14 @@ function system_update_8001() { } /** - * Set Bartik as default theme if it implicitly was the default before. + * Set 'node' as front page path and Bartik as default theme if it implicitly was before. + * + * Node module became optional. The default front page path was changed to + * 'user'. Since 'node' was the implicit default front page path previously and + * may not have been explicitly configured as such, this update ensures that the + * old implicit default is still the default. + * + * @see http://drupal.org/node/375397 * * The default theme for Drupal core was changed from Bartik to Stark. * Installation profiles (including Standard and Minimal) were changed to @@ -1723,8 +1741,14 @@ function system_update_8001() { * default. * * @see http://drupal.org/node/1181776 + * + * @ingroup config_upgrade */ function system_update_8002() { + $front_page = variable_get('site_frontpage'); + if (!isset($front_page)) { + variable_set('site_frontpage', 'node'); + } $theme = variable_get('theme_default'); if (!isset($theme)) { variable_set('theme_default', 'bartik'); @@ -1864,16 +1888,30 @@ function system_update_8008() { /** * Moves cron system settings from variable to config. + * + * @ingroup config_upgrade */ function system_update_8009() { - update_variables_to_config('system.cron'); + update_variables_to_config('system.cron', array( + 'cron_max_threshold' => 'cron_max_threshold', + 'cron_safe_threshold' => 'cron_safe_threshold', + 'cron_threshold_warning' => 'cron_threshold_warning', + 'cron_threshold_error' => 'cron_threshold_error', + 'cron_key' => 'cron_key', + )); } /** - * Moves system settings from variable to config. + * Moves RSS system settings from variable to config. + * + * @ingroup config_upgrade */ function system_update_8010() { - update_variables_to_config('system.rss-publishing'); + update_variables_to_config('system.rss-publishing', array( + 'feed_description' => 'feed_description', + 'feed_default_items' => 'feed_default_items', + 'feed_item_length' => 'feed_item_length', + )); } /** diff --git a/core/modules/system/tests/modules/config_upgrade/config/config.test.yml b/core/modules/system/tests/modules/config_upgrade/config/config.test.yml deleted file mode 100644 index 4e5eb16..0000000 --- a/core/modules/system/tests/modules/config_upgrade/config/config.test.yml +++ /dev/null @@ -1,2 +0,0 @@ -config_test_foo: bar -config_test_bar: foo diff --git a/core/modules/system/tests/modules/config_upgrade/config/config_upgrade.test.yml b/core/modules/system/tests/modules/config_upgrade/config/config_upgrade.test.yml new file mode 100644 index 0000000..3957538 --- /dev/null +++ b/core/modules/system/tests/modules/config_upgrade/config/config_upgrade.test.yml @@ -0,0 +1,4 @@ +parent: + bar: Bar + baz: Baz +foo: Foo diff --git a/core/modules/system/tests/upgrade/upgrade.test b/core/modules/system/tests/upgrade/upgrade.test index 2f942fa..429bddb 100644 --- a/core/modules/system/tests/upgrade/upgrade.test +++ b/core/modules/system/tests/upgrade/upgrade.test @@ -128,6 +128,7 @@ abstract class UpgradePathTestCase extends WebTestBase { $this->prepareD8Session(); // Restore necessary variables. + // @todo Convert into config('system.site')->set('mail')? $this->variable_set('site_mail', 'simpletest@example.com'); drupal_set_time_limit($this->timeLimit); @@ -142,6 +143,8 @@ abstract class UpgradePathTestCase extends WebTestBase { * @param $value * The value to set. This can be any PHP data type; these functions take care * of serialization as necessary. + * + * @todo Update for D8 configuration system. */ protected function variable_set($name, $value) { db_delete('variable')