diff --git a/core/core.services.yml b/core/core.services.yml index 02da722..399306d 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -67,16 +67,12 @@ services: factory_method: get factory_service: cache_factory arguments: [data] - config.cachedstorage.storage: - class: Drupal\Core\Config\FileStorage - factory_class: Drupal\Core\Config\FileStorageFactory - factory_method: getActive config.manager: class: Drupal\Core\Config\ConfigManager arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage'] config.storage: - class: Drupal\Core\Config\CachedStorage - arguments: ['@config.cachedstorage.storage', '@cache.config'] + class: Drupal\Core\Config\DatabaseStorage + arguments: ['@database', 'config'] config.factory: class: Drupal\Core\Config\ConfigFactory tags: diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index aa158a2..1900afc 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -439,7 +439,7 @@ function install_begin_request(&$install_state) { // Ensure that the active configuration directory is empty before installation // starts. if ($install_state['config_verified'] && empty($task)) { - $config = glob(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY) . '/*.' . FileStorage::getFileExtension()); + $config = Database::getConnection()->schema()->tableExists('config');; if (!empty($config)) { $task = NULL; throw new AlreadyInstalledException($container->get('string_translation')); diff --git a/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php b/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php index e075261..1df8f07 100644 --- a/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php +++ b/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config; use Drupal\Component\Utility\Settings; +use Drupal\Core\Database\Database; /** * Defines a factory for retrieving the config storage used pre-kernel. @@ -26,7 +27,7 @@ public static function get() { return call_user_func($drupal_bootstrap_config_storage); } else { - return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + return new DatabaseStorage(Database::getConnection(), 'config'); } } diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 19899a5..ff99c27 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\String; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -215,6 +216,11 @@ public function save() { $this->data[$key] = $this->castValue($key, $value); } } + else { + foreach ($this->data as $key => $value) { + $this->validateValue($key, $value); + } + } $this->storage->write($this->name, $this->data); $this->isNew = FALSE; diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index fd8f2ee..aa0e022 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -176,7 +176,7 @@ public function decode($raw) { */ public function listAll($prefix = '') { return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array( - ':name' => db_like($prefix) . '%', + ':name' => $this->connection->escapeLike($prefix) . '%', ), $this->options)->fetchCol(); } diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php index 8aa292b..18c0f07 100644 --- a/core/lib/Drupal/Core/Config/StorableConfigBase.php +++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php @@ -132,6 +132,27 @@ protected function getSchemaWrapper() { } /** + * Validate the values are allowed data types. + * + * @throws UnsupportedDataTypeConfigException + * If there is any invalid value. + */ + protected function validateValue($key, $value) { + // Minimal validation. Should not try to serialize resources or non-arrays. + if (is_array($value)) { + foreach ($value as $nested_value_key => $nested_value) { + $this->validateValue($key . '.' . $nested_value_key, $nested_value); + } + } + elseif ($value !== NULL && !is_scalar($value)) { + throw new UnsupportedDataTypeConfigException(String::format('Invalid data type for config element @name:@key', array( + '@name' => $this->getName(), + '@key' => $key, + ))); + } + } + + /** * Casts the value to correct data type using the configuration schema. * * @param string $key diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php index aa3c709..8d84bd6 100644 --- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php +++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php @@ -31,8 +31,9 @@ public function register(ContainerBuilder $container) { // Prevent config from accessing {cache_config}. // @see $conf['cache_classes'], update_prepare_d8_bootstrap() $container - ->register('config.storage', 'Drupal\Core\Config\FileStorage') - ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + ->register('config.storage', 'Drupal\Core\Config\DataBaseStorage') + ->addArgument(new Reference('database')) + ->addArgument('config'); $container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler') ->addArgument('%container.modules%'); $container diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 18f6287..c79b8cb 100644 --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -12,6 +12,7 @@ use Drupal\Core\Config\StorageInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\system\FileDownloadController; +use Symfony\Component\Yaml\Dumper; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -81,13 +82,15 @@ public function __construct(StorageInterface $target_storage, StorageInterface $ * Downloads a tarball of the site configuration. */ public function downloadExport() { + file_unmanaged_delete(file_directory_temp() . '/config.tar.gz'); + + $dumper = new Dumper(); + $dumper->setIndentation(2); + $archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz'); - $config_dir = config_get_config_directory(); - $config_files = array(); - foreach (\Drupal::service('config.storage')->listAll() as $config_name) { - $config_files[] = $config_dir . '/' . $config_name . '.yml'; + foreach (\Drupal::service('config.storage')->listAll() as $name) { + $archiver->addString("$name.yml", $dumper->dump(\Drupal::config($name)->get(), PHP_INT_MAX, 0, TRUE)); } - $archiver->createModify($config_files, '', config_get_config_directory()); $request = new Request(array('file' => 'config.tar.gz')); return $this->fileDownloadController->download($request, 'temporary'); diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php index 101dacb..4b2b6f7 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Yaml\Dumper; /** * Provides a form for exporting a single configuration file. @@ -33,6 +34,13 @@ class ConfigSingleExportForm extends FormBase { protected $configStorage; /** + * The YAML dumper. + * + * @var \Symfony\Component\Yaml\Dumper + */ + protected $dumper; + + /** * Tracks the valid config entity type definitions. * * @var \Drupal\Core\Entity\EntityTypeInterface[] @@ -46,10 +54,14 @@ class ConfigSingleExportForm extends FormBase { * The entity manager. * @param \Drupal\Core\Config\StorageInterface $config_storage * The config storage. + * @param \Symfony\Component\Yaml\Dumper $dumper + * The yaml dumper. */ - public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) { + public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, Dumper $dumper) { $this->entityManager = $entity_manager; $this->configStorage = $config_storage; + $this->dumper = $dumper; + $this->dumper->setIndentation(2); } /** @@ -58,7 +70,8 @@ public function __construct(EntityManagerInterface $entity_manager, StorageInter public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), - $container->get('config.storage') + $container->get('config.storage'), + new Dumper() ); } @@ -151,8 +164,7 @@ public function updateExport($form, &$form_state) { $name = $form_state['values']['config_name']; } // Read the raw data for this config name, encode it, and display it. - $data = $this->configStorage->read($name); - $form['export']['#value'] = $this->configStorage->encode($data); + $form['export']['#value'] = $this->dumper->dump($this->configStorage->read($name), PHP_INT_MAX, 0, TRUE); $form['export']['#description'] = $this->t('The filename is %name.', array('%name' => $name . '.yml')); return $form['export']; } diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php index 1295862..7b3643c 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\ConfirmFormBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Yaml\Yaml; /** * Provides a form for importing a single configuration file. @@ -32,6 +33,12 @@ class ConfigSingleImportForm extends ConfirmFormBase { protected $configStorage; /** + * The YAML component. + * + * @var \Symfony\Component\Yaml\Yaml + */ + protected $yaml; + /** * If the config exists, this is that object. Otherwise, FALSE. * * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool @@ -52,10 +59,13 @@ class ConfigSingleImportForm extends ConfirmFormBase { * The entity manager. * @param \Drupal\Core\Config\StorageInterface $config_storage * The config storage. + * @param \Symfony\Component\Yaml\Parser $parser + * The yaml dumper. */ - public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) { + public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, Yaml $yaml) { $this->entityManager = $entity_manager; $this->configStorage = $config_storage; + $this->yaml = $yaml; } /** @@ -64,7 +74,8 @@ public function __construct(EntityManagerInterface $entity_manager, StorageInter public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), - $container->get('config.storage') + $container->get('config.storage'), + new Yaml() ); } @@ -184,7 +195,7 @@ public function validateForm(array &$form, array &$form_state) { } // Decode the submitted import. - $data = $this->configStorage->decode($form_state['values']['import']); + $data = $this->yaml->parse($form_state['values']['import']); // Validate for config entities. if ($form_state['values']['config_type'] !== 'system.simple') { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index 7576163..2375608 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Config\ConfigNameException; use Drupal\simpletest\DrupalUnitTestBase; use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\UnsupportedDataTypeConfigException; /** @@ -192,10 +193,11 @@ function testNameValidation() { */ public function testDataTypes() { \Drupal::moduleHandler()->install(array('config_test')); - $storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + //$storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + $storage = new DatabaseStorage($this->container->get('database'), 'config'); $name = 'config_test.types'; $config = $this->container->get('config.factory')->get($name); - $original_content = file_get_contents($storage->getFilePath($name)); + $original_content = file_get_contents(drupal_get_path('module', 'config_test') . "/config/$name.yml"); $this->verbose('
' . $original_content . "\n" . var_export($storage->read($name), TRUE));
 
     // Verify variable data types are intact.
@@ -220,7 +222,7 @@ public function testDataTypes() {
     $this->assertIdentical($config->get(), $data);
     // Assert the data against the file storage.
     $this->assertIdentical($storage->read($name), $data);
-    $this->verbose('
' . file_get_contents($storage->getFilePath($name)) . var_export($storage->read($name), TRUE));
+    $this->verbose('
' . $name . var_export($storage->read($name), TRUE));
 
     // Set data using config::setData().
     $config->setData($data)->save();
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
index 46a14d4..5f1a416 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
@@ -130,6 +130,7 @@ public function testImportSimpleConfiguration() {
    */
   public function testExport() {
     $this->drupalLogin($this->drupalCreateUser(array('export configuration')));
+    $yaml = new Yaml();
 
     $this->drupalGet('admin/config/development/configuration/single/export/system.simple');
     $this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Simple configuration'), 'The simple configuration option is selected when specified in the URL.');
@@ -152,7 +153,7 @@ public function testExport() {
     $this->assertFieldByXPath('//select[@name="config_name"]//option[@selected="selected"]', t('Fallback date format'), 'The fallback date format config entity is selected when specified in the URL.');
 
     $fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback');
-    $data = \Drupal::service('config.storage')->encode($fallback_date->toArray());
+    $data = $yaml->dump($fallback_date->toArray(), 2, 2);
     $this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.');
   }
 
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
index 405aaf5..cb07cd4 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
@@ -368,7 +368,6 @@ public function testContactConfigEntityTranslation() {
    */
   public function testDateFormatTranslation() {
     $this->drupalLogin($this->admin_user);
-    $file_storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
 
     $this->drupalGet('admin/config/regional/date-time');
 
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index 7737cc8..1dc0523 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -103,9 +103,15 @@ protected function beforePrepareEnvironment() {
    *
    * @see config_get_config_directory()
    */
-  protected function prepareConfigDirectories() {
+  protected function prepareConfigStorage() {
     $this->configDirectories = array();
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    // Create the configuration table.
+    module_load_install('system');
+    $schema = system_schema();
+    // We use a different table name here and in the container to avoid
+    // exceptions when system module is installed.
+    Database::getConnection()->schema()->createTable('test_base_config', $schema['config']);
     foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
       // Assign the relative path to the global variable.
       $path = $this->siteDirectory . '/config_' . $type;
@@ -127,9 +133,6 @@ protected function setUp() {
 
     parent::setUp();
 
-    // Create and set new configuration directories.
-    $this->prepareConfigDirectories();
-
     // Build a minimal, partially mocked environment for unit tests.
     $this->containerBuild(\Drupal::getContainer());
     // Make sure it survives kernel rebuilds.
@@ -137,7 +140,8 @@ protected function setUp() {
 
     \Drupal::state()->set('system.module.files', $this->moduleFiles);
     \Drupal::state()->set('system.theme.files', $this->themeFiles);
-
+    // Create and set new configuration directories.
+    $this->prepareConfigStorage();
     // Bootstrap the kernel.
     // No need to dump it; this test runs in-memory.
     $this->kernel = new DrupalKernel('unit_testing', drupal_classloader(), FALSE);
@@ -225,8 +229,9 @@ public function containerBuild(ContainerBuilder $container) {
     $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
 
     $container
-      ->register('config.storage', 'Drupal\Core\Config\FileStorage')
-      ->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
+      ->register('config.storage', 'Drupal\Core\Config\DatabaseStorage')
+      ->addArgument(Database::getConnection())
+      ->addArgument('test_base_config');
 
     $this->settingsSet('keyvalue_default', 'keyvalue.memory');
     $container->set('keyvalue.memory', $this->keyValueFactory);
diff --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php
index aaf9612..3e5e9a4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php
@@ -30,7 +30,7 @@ function setUp() {
     // invoke DrupalUnitTestBase::setUp(), since that would set up further
     // environment aspects, which would distort this test, because it tests
     // the DrupalKernel (re-)building itself.
-    $this->prepareConfigDirectories();
+    $this->prepareConfigStorage();
 
     $this->settingsSet('php_storage', array('service_container' => array(
       'bin' => 'service_container',
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ca5d128..0bd1942 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -970,7 +970,25 @@ function system_schema() {
       'source_langcode_pid' => array('source', 'langcode', 'pid'),
     ),
   );
-
+  $schema['config'] = array(
+    'description' => 'The base table for configuration data.',
+    'fields' => array(
+      'name' => array(
+        'description' => 'Primary Key: Unique config object name.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'data' => array(
+        'description' => 'A serialized configuration object data.',
+        'type' => 'blob',
+        'not null' => FALSE,
+        'size' => 'big',
+      ),
+    ),
+    'primary key' => array('name'),
+  );
   $schema['config_snapshot'] = array(
     'description' => 'Stores a snapshot of the last imported configuration.',
     'fields' => array(