diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index d28d1e3..2cf982e 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2404,20 +2404,24 @@ function drupal_get_bootstrap_phase() { * * @see Drupal\Core\DrupalKernel * - * @param $reset - * A new container instance to reset the Drupal container to. + * @param Symfony\Component\DependencyInjection\Container $new_container + * A new container instance to replace the current. + * @param bool $reset + * (optional) Internal use only. Whether to enforce a reset of the statically + * cached container. Pass NULL for $new_container to create a fresh Container + * in a subsequent call to this function. Used by tests. * * @return Symfony\Component\DependencyInjection\Container * The instance of the Container used to set up and maintain object * instances. */ -function drupal_container(Container $reset = NULL) { +function drupal_container(Container $new_container = NULL, $reset = FALSE) { // We do not use drupal_static() here because we do not have a mechanism by // which to reinitialize the stored objects, so a drupal_static_reset() call // would leave Drupal in a nonfunctional state. static $container = NULL; - if (isset($reset)) { - $container = $reset; + if (isset($new_container) || $reset) { + $container = $new_container; } elseif (!isset($container)) { // Return a ContainerBuilder instance with the bare essentials needed for any @@ -2426,25 +2430,38 @@ function drupal_container(Container $reset = NULL) { // requests. $container = new ContainerBuilder(); - // Register configuration storage class and options. + // Register active configuration storage. // @todo The active store and its options need to be configurable. // Use either global $conf (recursion warning) or global $config, or a // bootstrap configuration *file* to allow to set/override this very // lowest of low level configuration. - $container->setParameter('config.storage.options', array( - 'connection' => 'default', - 'target' => 'default', + $container->setParameter('config.storage.active.options', array( + 'Drupal\Core\Config\FileStorage' => array( + 'directory' => config_get_config_directory(CONFIG_ACTIVE_DIRECTORY), + ), + 'Drupal\Core\Config\CacheStorage' => array( + 'backend' => 'Drupal\Core\Cache\DatabaseBackend', + 'bin' => 'config', + ), )); - $container->register('config.storage', 'Drupal\Core\Config\DatabaseStorage') - ->addArgument('%config.storage.options%'); + $container->register('config.storage.active', 'Drupal\Core\Config\CachedFileStorage') + ->addArgument('%config.storage.active.options%'); $container->register('config.subscriber.globalconf', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber'); $container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher') ->addMethodCall('addSubscriber', array(new Reference('config.subscriber.globalconf'))); + // Register configuration object factory. $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') - ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('config.storage.active')) ->addArgument(new Reference('dispatcher')); + + // Register staging configuration storage. + $container->setParameter('config.storage.staging.options', array( + 'directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY), + )); + $container->register('config.storage.staging', 'Drupal\Core\Config\FileStorage') + ->addArgument('%config.storage.staging.options%'); } return $container; } diff --git a/core/includes/config.inc b/core/includes/config.inc index 369a0d1..2bc77c0 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -17,15 +17,12 @@ * The extension type; e.g., 'module' or 'theme'. * @param string $name * The name of the module or theme to install default configuration for. - * - * @todo Make this acknowledge other storage engines rather than having - * SQL be hardcoded. */ function config_install_default_config($type, $name) { $config_dir = drupal_get_path($type, $name) . '/config'; if (is_dir($config_dir)) { $source_storage = new FileStorage(array('directory' => $config_dir)); - $target_storage = drupal_container()->get('config.storage'); + $target_storage = drupal_container()->get('config.storage.active'); $null_storage = new NullStorage(); // Upon installation, only new config objects need to be created. @@ -43,10 +40,12 @@ function config_install_default_config($type, $name) { } /** - * @todo Modules need a way to access the active store, whatever it is. + * Gets configuration object names starting with a given prefix. + * + * @see Drupal\Core\Config\StorageInterface::listAll() */ function config_get_storage_names_with_prefix($prefix = '') { - return drupal_container()->get('config.storage')->listAll($prefix); + return drupal_container()->get('config.storage.active')->listAll($prefix); } /** @@ -137,8 +136,8 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto */ function config_import() { // Retrieve a list of differences between staging and the active store. - $source_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY))); - $target_storage = drupal_container()->get('config.storage'); + $source_storage = drupal_container()->get('config.storage.staging'); + $target_storage = drupal_container()->get('config.storage.active'); $config_changes = config_sync_get_changes($source_storage, $target_storage); if (empty($config_changes)) { @@ -218,8 +217,8 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou */ function config_export() { // Retrieve a list of differences between the active store and staging. - $source_storage = drupal_container()->get('config.storage'); - $target_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY))); + $source_storage = drupal_container()->get('config.storage.active'); + $target_storage = drupal_container()->get('config.storage.staging'); $config_changes = config_sync_get_changes($source_storage, $target_storage); if (empty($config_changes)) { diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index c15107c..039f007 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -5,6 +5,8 @@ use Drupal\Core\Database\Install\TaskException; use Drupal\Core\Language\Language; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -272,6 +274,42 @@ function install_begin_request(&$install_state) { include_once DRUPAL_ROOT . '/core/includes/module.inc'; include_once DRUPAL_ROOT . '/core/includes/session.inc'; + // Determine whether the configuration system is ready to operate. + $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY); + + // If it is not, replace the configuration storage with the InstallStorage + // implementation, for the following reasons: + // - The first call into drupal_container() will try to set up the regular + // runtime configuration storage, using the CachedFileStorage. The storage + // controller options require to pass in the config directory name, but that + // directory does not exist yet. + // - The installer outputs maintenance theme pages and performs many other + // operations, which try to load configuration. Since there is no active + // configuration yet, and because the configuration system does not have a + // notion of default values at runtime, data is missing in many places. The + // lack of data does not trigger errors, but results in a broken user + // interface (e.g., missing page title, etc). + // - The actual configuration data to read during installation is essentially + // the default configuration provided by the installation profile and + // modules (most notably System module). The InstallStorage therefore reads + // from the default configuration directories of extensions. + // This override is reverted as soon as the config directory has been set up + // successfully. + // @see drupal_install_config_directory() + // @todo Move this into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder. + if (!$install_state['config_verified']) { + $container = new ContainerBuilder(); + + $container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher'); + + $container->register('config.storage.active', 'Drupal\Core\Config\InstallStorage'); + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') + ->addArgument(new Reference('config.storage.active')) + ->addArgument(new Reference('dispatcher')); + + drupal_container($container); + } + // Set up $language, so t() caller functions will still work. drupal_language_initialize(); @@ -316,7 +354,6 @@ function install_begin_request(&$install_state) { // Check existing settings.php. $install_state['database_verified'] = install_verify_database_settings(); - $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY); $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified']; if ($install_state['database_verified']) { @@ -1054,6 +1091,11 @@ function install_settings_form_submit($form, &$form_state) { // Add the config directories to settings.php. drupal_install_config_directories(); + // We have valid configuration directories in settings.php. + // Reset the service container, so the config.storage.active service will use + // the actual active storage for installing configuration. + drupal_container(NULL, TRUE); + // Indicate that the settings file has been verified, and check the database // for the last completed task, now that we have a valid connection. This // last step is important since we want to trigger an error if the new diff --git a/core/includes/install.inc b/core/includes/install.inc index 2c95bf3..3ae0144 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -7,6 +7,7 @@ use Drupal\Core\Database\Database; use Drupal\locale\Gettext; +use Exception; /** * Indicates that a module has not been installed yet. @@ -275,7 +276,7 @@ function drupal_install_config_directories() { drupal_rewrite_settings($settings); } - // Ensure that the config directory exists or can be created, and is writable. + // Ensure the config directories exist or can be created, and are writable. foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $config_type) { // This should never fail, since if the config directory was specified in // settings.php it will have already been created and verified earlier, and @@ -296,7 +297,7 @@ function drupal_install_config_directories() { * Checks whether a config directory name is defined, and if so, whether it * exists and is writable. * - * This partically duplicates install_ensure_config_directory(), but is required + * This partially duplicates install_ensure_config_directory(), but is required * since the installer would create the config directory too early in the * installation process otherwise (e.g., when only visiting install.php when * there is a settings.php already, but not actually executing the installation). @@ -313,9 +314,16 @@ function install_verify_config_directory($type) { if (!isset($config_directories[$type])) { return FALSE; } - $config_directory = config_get_config_directory($type); - if (is_dir($config_directory) && is_writable($config_directory)) { - return TRUE; + // config_get_config_directory() throws an exception when there is a prepared + // settings.php that defines $config_directories already and the directories + // do not exist yet. + try { + $config_directory = config_get_config_directory($type); + if (is_dir($config_directory) && is_writable($config_directory)) { + return TRUE; + } + } + catch (Exception $e) { } return FALSE; } diff --git a/core/includes/module.inc b/core/includes/module.inc index d9eab10..c3399c0 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -6,7 +6,7 @@ */ use Drupal\Component\Graph\Graph; -use Drupal\Core\Config\DatabaseStorage; +use Drupal\Core\Config\NullStorage; /** * Loads all the modules that have been enabled in the system table. @@ -617,19 +617,26 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) $module_list = array_keys($module_list); } - $storage = new DatabaseStorage(); + $source_storage = new NullStorage(); + $target_storage = drupal_container()->get('config.storage.active'); foreach ($module_list as $module) { + // Remove all configuration belonging to the module. + $config_changes = $target_storage->listAll($module . '.'); + if (!empty($config_changes)) { + $config_changes = array( + 'delete' => $config_changes, + 'change' => array(), + 'create' => array(), + ); + $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); + config_sync_changes($remaining_changes, $source_storage, $target_storage); + } + // Uninstall the module. module_load_install($module); module_invoke($module, 'uninstall'); drupal_uninstall_schema($module); - // Remove all configuration belonging to the module. - $config_names = $storage->listAll($module . '.'); - foreach ($config_names as $config_name) { - config($config_name)->delete(); - } - watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); } diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index 12de2a7..4b3e80c 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -122,7 +122,7 @@ function theme_task_list($variables) { } else { $class = $done ? 'done' : ''; - $status = $done ? '(' . t('done') . ')' : ''; + $status = $done ? '(' . $t('done') . ')' : ''; } $output .= ''; diff --git a/core/lib/Drupal/Core/Config/CacheStorage.php b/core/lib/Drupal/Core/Config/CacheStorage.php new file mode 100644 index 0000000..a2effc8 --- /dev/null +++ b/core/lib/Drupal/Core/Config/CacheStorage.php @@ -0,0 +1,143 @@ + 'Drupal\Core\Cache\DatabaseBackend', + 'bin' => 'config', + ); + $this->options = $options; + } + + /** + * Returns the instantiated Cache backend to use. + */ + protected function getBackend() { + if (!isset($this->storage)) { + $this->storage = new $this->options['backend']($this->options['bin']); + } + return $this->storage; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + return (bool) $this->getBackend()->get($name); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::read(). + */ + public function read($name) { + if ($cache = $this->getBackend()->get($name)) { + // The cache backend supports primitive data types, but only an array + // represents valid config object data. + if (is_array($cache->data)) { + return $cache->data; + } + } + return FALSE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::write(). + */ + public function write($name, array $data) { + $this->getBackend()->set($name, $data, CacheBackendInterface::CACHE_PERMANENT, array('config' => array($name))); + return TRUE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::delete(). + */ + public function delete($name) { + $this->getBackend()->delete($name); + return TRUE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::rename(). + */ + public function rename($name, $new_name) { + $this->getBackend()->delete($name); + $this->getBackend()->delete($new_name); + return TRUE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::encode(). + */ + public static function encode($data) { + return serialize($data); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::decode(). + * + * @throws ErrorException + * unserialize() triggers E_NOTICE if the string cannot be unserialized. + */ + public static function decode($raw) { + $data = @unserialize($raw); + return is_array($data) ? $data : FALSE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + * + * Not supported by CacheBackendInterface. + */ + public function listAll($prefix = '') { + return array(); + } +} diff --git a/core/lib/Drupal/Core/Config/CachedFileStorage.php b/core/lib/Drupal/Core/Config/CachedFileStorage.php new file mode 100644 index 0000000..0fc0cfe --- /dev/null +++ b/core/lib/Drupal/Core/Config/CachedFileStorage.php @@ -0,0 +1,134 @@ +options = $options; + + $this->storages['file'] = new FileStorage($options['Drupal\Core\Config\FileStorage']); + + unset($options['Drupal\Core\Config\FileStorage']); + list($cache_class, $cache_options) = each($options); + $this->storages['cache'] = new $cache_class($cache_options); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + // A single filestat is faster than a complex cache lookup and possibly + // subsequent filestat. + return $this->storages['file']->exists($name); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::read(). + */ + public function read($name) { + // Check the cache. + $data = $this->storages['cache']->read($name); + // If the cache returns no result, check the file storage. + if ($data === FALSE) { + $data = $this->storages['file']->read($name); + // @todo Should the config object be cached if it does not exist? + if ($data !== FALSE) { + $this->storages['cache']->write($name, $data); + } + } + return $data; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::write(). + */ + public function write($name, array $data) { + $success = $this->storages['file']->write($name, $data); + $this->storages['cache']->delete($name); + return $success; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::delete(). + */ + public function delete($name) { + $success = TRUE; + foreach ($this->storages as $storage) { + if (!$storage->delete($name)) { + $success = FALSE; + } + } + return $success; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::rename(). + */ + public function rename($name, $new_name) { + $success = $this->storages['file']->rename($name, $new_name); + $this->storages['cache']->rename($name, $new_name); + return $success; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::encode(). + * + * @todo Remove encode() from StorageInterface. + */ + public static function encode($data) { + return $this->storages['file']->encode($data); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::decode(). + * + * @todo Remove decode() from StorageInterface. + */ + public static function decode($raw) { + return $this->storages['file']->decode($raw); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + */ + public function listAll($prefix = '') { + return $this->storages['file']->listAll($prefix); + } +} diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index ad8c155..e4a1934 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -44,6 +44,15 @@ protected function getConnection() { } /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + return (bool) $this->getConnection()->queryRange('SELECT 1 FROM {config} WHERE name = :name', 0, 1, array( + ':name' => $name, + ), $this->options)->fetchField(); + } + + /** * Implements Drupal\Core\Config\StorageInterface::read(). * * @throws PDOException @@ -56,8 +65,6 @@ public function read($name) { // read without actually having the database available. In this case, // catch the exception and just return an empty array so the caller can // handle it if need be. - // @todo Remove this and use appropriate config.storage service definition - // in the installer instead. try { $raw = $this->getConnection()->query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name), $this->options)->fetchField(); if ($raw !== FALSE) { diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index d96af7e..cbad07f 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -28,7 +28,7 @@ class FileStorage implements StorageInterface { */ public function __construct(array $options = array()) { if (!isset($options['directory'])) { - $options['directory'] = config_get_config_directory(); + $options['directory'] = config_get_config_directory(CONFIG_ACTIVE_DIRECTORY); } $this->options = $options; } @@ -54,10 +54,7 @@ public static function getFileExtension() { } /** - * Returns whether the configuration file exists. - * - * @return bool - * TRUE if the configuration file exists, FALSE otherwise. + * Implements Drupal\Core\Config\StorageInterface::exists(). */ public function exists($name) { return file_exists($this->getFilePath($name)); diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php new file mode 100644 index 0000000..b2ee894 --- /dev/null +++ b/core/lib/Drupal/Core/Config/InstallStorage.php @@ -0,0 +1,105 @@ +options = $options; + } + + /** + * Overrides Drupal\Core\Config\FileStorage::getFilePath(). + * + * Returns the path to the configuration file. + * + * Determines the owner and path to the default configuration file of a + * requested config object name located in the installation profile, a module, + * or a theme (in this order). + * + * @return string + * The path to the configuration file. + * + * @todo Improve this when figuring out how we want to handle configuration in + * installation profiles. E.g., a config object actually has to be searched + * in the profile first (whereas the profile is never the owner), only + * 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; + } + } + } + // If any code in the early installer requests a configuration object that + // does not exist anywhere as default config, then that must be mistake. + throw new StorageException(format_string('Missing configuration file: @name', array( + '@name' => $name, + ))); + } + + /** + * Overrides Drupal\Core\Config\FileStorage::write(). + * + * @throws Drupal\Core\Config\StorageException + */ + public function write($name, array $data) { + throw new StorageException('Write operations are not allowed.'); + } + + /** + * Overrides Drupal\Core\Config\FileStorage::delete(). + * + * @throws Drupal\Core\Config\StorageException + */ + public function delete($name) { + throw new StorageException('Write operations are not allowed.'); + } + + /** + * Overrides Drupal\Core\Config\FileStorage::rename(). + * + * @throws Drupal\Core\Config\StorageException + */ + public function rename($name, $new_name) { + throw new StorageException('Write operations are not allowed.'); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + * + * @throws Drupal\Core\Config\StorageException + */ + public function listAll($prefix = '') { + throw new StorageException('List operation is not supported.'); + } +} diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php index ea21be1..aa81b73 100644 --- a/core/lib/Drupal/Core/Config/NullStorage.php +++ b/core/lib/Drupal/Core/Config/NullStorage.php @@ -29,6 +29,13 @@ public function __construct(array $options = array()) { } /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + return FALSE; + } + + /** * Implements Drupal\Core\Config\StorageInterface::read(). */ public function read($name) { diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index a466538..688ae46 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -25,6 +25,17 @@ public function __construct(array $options = array()); /** + * Returns whether a configuration object exists. + * + * @param string $name + * The name of a configuration object to test. + * + * @return bool + * TRUE if the configuration object exists, FALSE otherwise. + */ + public function exists($name); + + /** * Reads configuration data from the storage. * * @param string $name diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc new file mode 100644 index 0000000..e41dcb8 --- /dev/null +++ b/core/modules/config/config.admin.inc @@ -0,0 +1,132 @@ + t('There are no configuration changes.'), + ); + return $form; + } + + foreach ($config_changes as $config_change_type => $config_files) { + if (empty($config_files)) { + continue; + } + $form[$config_change_type] = array( + '#type' => 'fieldset', + '#title' => $config_change_type . ' (' . count($config_files) . ')', + '#collapsible' => TRUE, + ); + $form[$config_change_type]['config_files'] = array( + '#theme' => 'table', + '#header' => array('Name'), + ); + foreach ($config_files as $config_file) { + $form[$config_change_type]['config_files']['#rows'][] = array($config_file); + } + } +} + +/** + * Form constructor for configuration import form. + * + * @see config_admin_import_form_submit() + * @see config_import() + */ +function config_admin_import_form($form, &$form_state) { + // Retrieve a list of differences between last known state and active store. + $source_storage = drupal_container()->get('config.storage.staging'); + $target_storage = drupal_container()->get('config.storage.active'); + + // Prevent users from deleting all configuration. + // If the source storage is empty, that signals the unique condition of not + // having exported anything at all, and thus no valid storage to compare the + // active storage against. + // @todo StorageInterface::listAll() can easily yield hundreds or even + // thousands of entries; consider to add a dedicated isEmpty() method for + // storage controllers. + $all = $source_storage->listAll(); + if (empty($all)) { + form_set_error('', t('There is no base configuration. Export it first.', array( + '@export-url' => url('admin/config/development/sync/export'), + ))); + return $form; + } + + config_admin_sync_form($form, $form_state, $source_storage, $target_storage); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Form submission handler for config_admin_import_form(). + */ +function config_admin_import_form_submit($form, &$form_state) { + if (config_import()) { + drupal_set_message(t('The configuration was imported successfully.')); + } + else { + // Another request may be synchronizing configuration already. Wait for it + // to complete before returning the error, so already synchronized changes + // do not appear again. + lock_wait(__FUNCTION__); + drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error'); + } +} + +/** + * Form constructor for configuration export form. + * + * @see config_admin_export_form_submit() + * @see config_export() + * + * @todo "export" is a misnomer with config.storage.staging. + */ +function config_admin_export_form($form, &$form_state) { + // Retrieve a list of differences between active store and last known state. + $source_storage = drupal_container()->get('config.storage.active'); + $target_storage = drupal_container()->get('config.storage.staging'); + + config_admin_sync_form($form, $form_state, $source_storage, $target_storage); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Export'), + ); + return $form; +} + +/** + * Form submission handler for config_admin_export_form(). + */ +function config_admin_export_form_submit($form, &$form_state) { + config_export(); + drupal_set_message(t('The configuration was exported successfully.')); +} + diff --git a/core/modules/config/config.info b/core/modules/config/config.info index 380f17e..efab7a1 100644 --- a/core/modules/config/config.info +++ b/core/modules/config/config.info @@ -3,3 +3,4 @@ description = Allows administrators to manage configuration changes. package = Core version = VERSION core = 8.x +configure = admin/config/development/sync diff --git a/core/modules/config/config.module b/core/modules/config/config.module index b3d9bbc..fd38fd2 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -1 +1,46 @@ t('Synchronize configuration'), + 'restrict access' => TRUE, + ); + return $permissions; +} + +/** + * Implements hook_menu(). + */ +function config_menu() { + $items['admin/config/development/sync'] = array( + 'title' => 'Synchronize configuration', + 'description' => 'Synchronize configuration changes.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_admin_import_form'), + 'access arguments' => array('synchronize configuration'), + 'file' => 'config.admin.inc', + ); + $items['admin/config/development/sync/import'] = array( + 'title' => 'Import', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/config/development/sync/export'] = array( + 'title' => 'Export', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_admin_export_form'), + 'access arguments' => array('synchronize configuration'), + 'file' => 'config.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + return $items; +} + diff --git a/core/modules/config/lib/Drupal/config/ConfigStorageController.php b/core/modules/config/lib/Drupal/config/ConfigStorageController.php index 12d62b4..840c8fb 100644 --- a/core/modules/config/lib/Drupal/config/ConfigStorageController.php +++ b/core/modules/config/lib/Drupal/config/ConfigStorageController.php @@ -151,7 +151,7 @@ protected function buildQuery($ids, $revision_id = FALSE) { // Load all of the configurables. if ($ids === NULL) { - $names = drupal_container()->get('config.storage')->listAll($prefix); + $names = drupal_container()->get('config.storage.active')->listAll($prefix); $result = array(); foreach ($names as $name) { $config = config($name); @@ -273,7 +273,8 @@ public function save(StorableInterface $entity) { // Configuration objects do not have a schema. Extract all key names from // class properties. $class_info = new \ReflectionClass($entity); - foreach ($class_info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $properties = $class_info->getProperties(\ReflectionProperty::IS_PUBLIC); + foreach ($properties as $property) { $name = $property->getName(); $config->set($name, $entity->$name); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index a5d36a9..3a66d57 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -7,7 +7,6 @@ namespace Drupal\config\Tests; -use Drupal\Core\Config\DatabaseStorage; use Drupal\simpletest\WebTestBase; /** @@ -26,7 +25,7 @@ public static function getInfo() { * Tests CRUD operations. */ function testCRUD() { - $storage = new DatabaseStorage(); + $storage = $this->container->get('config.storage.active'); $name = 'config_test.crud'; $config = config($name); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php index abbd2ae..4a82d0b 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php @@ -7,7 +7,6 @@ namespace Drupal\config\Tests; -use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\FileStorage; use Drupal\simpletest\WebTestBase; @@ -23,15 +22,11 @@ public static function getInfo() { ); } - function setUp() { - parent::setUp(); - } - /** * Tests setting, writing, and reading of a configuration setting. */ function testReadWriteConfig() { - $database_storage = new DatabaseStorage(); + $storage = $this->container->get('config.storage.active'); $name = 'foo.bar'; $key = 'foo'; @@ -67,7 +62,7 @@ function testReadWriteConfig() { $this->assertEqual($config->get(), array(), t('New config object is empty.')); // Verify nothing was saved. - $db_data = $database_storage->read($name); + $db_data = $storage->read($name); $this->assertIdentical($db_data, FALSE); // Add a top level value @@ -94,7 +89,7 @@ function testReadWriteConfig() { $config->save(); // Verify the database entry exists. - $db_data = $database_storage->read($name); + $db_data = $storage->read($name); $this->assertTrue($db_data); // Read top level value @@ -152,27 +147,27 @@ function testReadWriteConfig() { $config->set($key, $value)->save(); // Verify the database entry exists from a chained save. - $db_data = $database_storage->read($chained_name); + $db_data = $storage->read($chained_name); $this->assertEqual($db_data, $config->get()); // Get file listing for all files starting with 'foo'. Should return // two elements. - $files = $database_storage->listAll('foo'); + $files = $storage->listAll('foo'); $this->assertEqual(count($files), 2, 'Two files listed with the prefix \'foo\'.'); // Get file listing for all files starting with 'biff'. Should return // one element. - $files = $database_storage->listAll('biff'); + $files = $storage->listAll('biff'); $this->assertEqual(count($files), 1, 'One file listed with the prefix \'biff\'.'); // Get file listing for all files starting with 'foo.bar'. Should return // one element. - $files = $database_storage->listAll('foo.bar'); + $files = $storage->listAll('foo.bar'); $this->assertEqual(count($files), 1, 'One file listed with the prefix \'foo.bar\'.'); // Get file listing for all files starting with 'bar'. Should return // an empty array. - $files = $database_storage->listAll('bar'); + $files = $storage->listAll('bar'); $this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.'); // Delete the configuration. @@ -180,7 +175,7 @@ function testReadWriteConfig() { $config->delete(); // Verify the database entry no longer exists. - $db_data = $database_storage->read($name); + $db_data = $storage->read($name); $this->assertIdentical($db_data, FALSE); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index 0c1fe68..8ad26f0 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -7,8 +7,6 @@ namespace Drupal\config\Tests; -use Drupal\Core\Config\DatabaseStorage; -use Drupal\Core\Config\FileStorage; use Drupal\simpletest\WebTestBase; /** @@ -64,8 +62,8 @@ function testNoImport() { function testDeleted() { $name = 'config_test.system'; $dynamic_name = 'config_test.dynamic.default'; - $active_storage = new DatabaseStorage(); - $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY))); + $storage = $this->container->get('config.storage.active'); + $staging = $this->container->get('config.storage.staging'); // Verify the default configuration values exist. $config = config($name); @@ -77,15 +75,15 @@ function testDeleted() { config_export(); // Delete the configuration objects from the staging directory. - $staging_storage->delete($name); - $staging_storage->delete($dynamic_name); + $staging->delete($name); + $staging->delete($dynamic_name); // Import. config_import(); // Verify the values have disappeared. - $this->assertIdentical($active_storage->read($name), FALSE); - $this->assertIdentical($active_storage->read($dynamic_name), FALSE); + $this->assertIdentical($storage->read($name), FALSE); + $this->assertIdentical($storage->read($dynamic_name), FALSE); $config = config($name); $this->assertIdentical($config->get('foo'), NULL); @@ -99,6 +97,9 @@ function testDeleted() { $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); $this->assertTrue(isset($GLOBALS['hook_config_test']['predelete'])); $this->assertTrue(isset($GLOBALS['hook_config_test']['delete'])); + + // Verify that there is nothing more to import. + $this->assertFalse(config_sync_get_changes($staging, $storage)); } /** @@ -107,27 +108,34 @@ function testDeleted() { function testNew() { $name = 'config_test.new'; $dynamic_name = 'config_test.dynamic.new'; - $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY))); + $storage = $this->container->get('config.storage.active'); + $staging = $this->container->get('config.storage.staging'); // Export. config_export(); // Verify the configuration to create does not exist yet. - $this->assertIdentical($staging_storage->exists($name), FALSE, $name . ' not found.'); - $this->assertIdentical($staging_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + $this->assertIdentical($storage->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + + $this->assertIdentical($staging->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($staging->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); // Create new configuration objects in the staging directory. $original_name_data = array( 'add_me' => 'new value', ); - $staging_storage->write($name, $original_name_data); + $staging->write($name, $original_name_data); $original_dynamic_data = array( 'id' => 'new', 'label' => 'New', + 'langcode' => 'und', + 'style' => '', + 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', ); - $staging_storage->write($dynamic_name, $original_dynamic_data); - $this->assertIdentical($staging_storage->exists($name), TRUE, $name . ' found.'); - $this->assertIdentical($staging_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + $staging->write($dynamic_name, $original_dynamic_data); + $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); // Import. config_import(); @@ -145,6 +153,9 @@ function testNew() { $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); + + // Verify that there is nothing more to import. + $this->assertFalse(config_sync_get_changes($staging, $storage)); } /** @@ -153,26 +164,28 @@ function testNew() { function testUpdated() { $name = 'config_test.system'; $dynamic_name = 'config_test.dynamic.default'; - $staging_storage = new FileStorage(array('directory' => config_get_config_directory(CONFIG_STAGING_DIRECTORY))); + $storage = $this->container->get('config.storage.active'); + $staging = $this->container->get('config.storage.staging'); // Export. config_export(); // Verify that the configuration objects to import exist. - $this->assertIdentical($staging_storage->exists($name), TRUE, $name . ' found.'); - $this->assertIdentical($staging_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + $this->assertIdentical($storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + + $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); // Replace the file content of the existing configuration objects in the // staging directory. $original_name_data = array( 'foo' => 'beer', ); - $staging_storage->write($name, $original_name_data); - $original_dynamic_data = array( - 'id' => 'default', - 'label' => 'Updated', - ); - $staging_storage->write($dynamic_name, $original_dynamic_data); + $staging->write($name, $original_name_data); + $original_dynamic_data = $staging->read($dynamic_name); + $original_dynamic_data['label'] = 'Updated'; + $staging->write($dynamic_name, $original_dynamic_data); // Verify the active store still returns the default values. $config = config($name); @@ -190,8 +203,8 @@ function testUpdated() { $this->assertIdentical($config->get('label'), 'Updated'); // Verify that the original file content is still the same. - $this->assertIdentical($staging_storage->read($name), $original_name_data); - $this->assertIdentical($staging_storage->read($dynamic_name), $original_dynamic_data); + $this->assertIdentical($staging->read($name), $original_name_data); + $this->assertIdentical($staging->read($dynamic_name), $original_dynamic_data); // Verify that appropriate module API hooks have been invoked. $this->assertTrue(isset($GLOBALS['hook_config_test']['load'])); @@ -200,6 +213,9 @@ function testUpdated() { $this->assertTrue(isset($GLOBALS['hook_config_test']['update'])); $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); + + // Verify that there is nothing more to import. + $this->assertFalse(config_sync_get_changes($staging, $storage)); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php new file mode 100644 index 0000000..8779734 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -0,0 +1,153 @@ + 'Import/Export UI', + 'description' => 'Tests the user interface for importing/exporting configuration.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp(); + + $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); + $this->drupalLogin($this->web_user); + } + + /** + * Tests exporting configuration. + */ + function testExport() { + $name = 'config_test.system'; + $dynamic_name = 'config_test.dynamic.default'; + + // Verify the default configuration values exist. + $config = config($name); + $this->assertIdentical($config->get('foo'), 'bar'); + $config = config($dynamic_name); + $this->assertIdentical($config->get('id'), 'default'); + + // Verify that both appear as deleted by default. + $this->drupalGet('admin/config/development/sync/export'); + $this->assertText($name); + $this->assertText($dynamic_name); + + // Export and verify that both do not appear anymore. + $this->drupalPost(NULL, array(), t('Export')); + $this->assertUrl('admin/config/development/sync/export'); + $this->assertNoText($name); + $this->assertNoText($dynamic_name); + + // Verify that there are no further changes to export. + $this->assertText(t('There are no configuration changes.')); + + // Verify that the import screen shows no changes either. + $this->drupalGet('admin/config/development/sync'); + $this->assertText(t('There are no configuration changes.')); + } + + /** + * Tests importing configuration. + */ + function testImport() { + $name = 'config_test.new'; + $dynamic_name = 'config_test.dynamic.new'; + $storage = $this->container->get('config.storage.active'); + $staging = $this->container->get('config.storage.staging'); + + // Verify the configuration to create does not exist yet. + $this->assertIdentical($storage->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + $this->assertIdentical($staging->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($staging->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + + // Verify that the import UI does not allow to import without exported + // configuration. + $this->drupalGet('admin/config/development/sync'); + $this->assertText('There is no base configuration.'); + + // Verify that the Export link yields to the export UI page, and export. + $this->clickLink('Export'); + $this->drupalPost(NULL, array(), t('Export')); + + // Create new configuration objects. + $original_name_data = array( + 'add_me' => 'new value', + ); + $staging->write($name, $original_name_data); + $original_dynamic_data = array( + 'id' => 'new', + 'label' => 'New', + 'langcode' => 'und', + 'style' => '', + 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', + ); + $staging->write($dynamic_name, $original_dynamic_data); + $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + + // Verify that both appear as new. + $this->drupalGet('admin/config/development/sync'); + $this->assertText($name); + $this->assertText($dynamic_name); + + // Import and verify that both do not appear anymore. + $this->drupalPost(NULL, array(), t('Import')); + $this->assertUrl('admin/config/development/sync'); + $this->assertNoText($name); + $this->assertNoText($dynamic_name); + + // Verify that there are no further changes to import. + $this->assertText(t('There are no configuration changes.')); + + // Verify that the export screen shows no changes either. + $this->drupalGet('admin/config/development/sync/export'); + $this->assertText(t('There are no configuration changes.')); + } + + /** + * Tests concurrent importing of configuration. + */ + function testImportLock() { + $name = 'config_test.new'; + $staging = $this->container->get('config.storage.staging'); + + // Write a configuration object to import. + $staging->write($name, array( + 'add_me' => 'new value', + )); + + // Verify that there are configuration differences to import. + $this->drupalGet('admin/config/development/sync'); + $this->assertNoText(t('There are no configuration changes.')); + + // Acquire a fake-lock on the import mechanism. + $lock_name = 'config_import'; + lock_acquire($lock_name); + + // Attempt to import configuration and verify that an error message appears. + $this->drupalPost(NULL, array(), t('Import')); + $this->assertUrl('admin/config/development/sync'); + $this->assertText(t('The import failed due to an error. Any errors have been logged.')); + + // Release the lock, just to keep testing sane. + lock_release($lock_name); + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php index d07b0b7..6b7a74f 100644 --- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php @@ -32,6 +32,9 @@ function testCRUD() { $name = 'config_test.storage'; + // Checking whether a non-existing name exists returns FALSE. + $this->assertIdentical($this->storage->exists($name), FALSE); + // Reading a non-existing name returns FALSE. $data = $this->storage->read($name); $this->assertIdentical($data, FALSE); @@ -51,9 +54,13 @@ function testCRUD() { $data = array('foo' => 'bar'); $result = $this->storage->write($name, $data); $this->assertIdentical($result, TRUE); + $raw_data = $this->read($name); $this->assertIdentical($raw_data, $data); + // Checking whether an existing name exists returns TRUE. + $this->assertIdentical($this->storage->exists($name), TRUE); + // Writing the identical data again still returns TRUE. $result = $this->storage->write($name, $data); $this->assertIdentical($result, TRUE); diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php index 2e1599f..185921f 100644 --- a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php @@ -23,8 +23,34 @@ public static function getInfo() { function setUp() { parent::setUp(); + + $schema['config'] = array( + 'description' => 'Default active store for the configuration system.', + 'fields' => array( + 'name' => array( + 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'description' => 'The raw data for this configuration entry.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'translatable' => TRUE, + ), + ), + 'primary key' => array('name'), + ); + db_create_table('config', $schema['config']); + $this->storage = new DatabaseStorage(); $this->invalidStorage = new DatabaseStorage(array('connection' => 'invalid')); + + // ::listAll() verifications require other configuration data to exist. + $this->storage->write('system.performance', array()); } protected function read($name) { diff --git a/core/modules/entity/lib/Drupal/entity/StorableBase.php b/core/modules/entity/lib/Drupal/entity/StorableBase.php index 57bf352..c5d9867 100644 --- a/core/modules/entity/lib/Drupal/entity/StorableBase.php +++ b/core/modules/entity/lib/Drupal/entity/StorableBase.php @@ -45,7 +45,7 @@ * * @var bool */ - public $isCurrentRevision = TRUE; + protected $isCurrentRevision = TRUE; /** * Constructs a new entity object. @@ -280,7 +280,11 @@ public function getRevisionId() { /** * Implements Drupal\entity\StorableInterface::isCurrentRevision(). */ - public function isCurrentRevision() { - return $this->isCurrentRevision; + public function isCurrentRevision($new_value = NULL) { + $return = $this->isCurrentRevision; + if (isset($new_value)) { + $this->isCurrentRevision = (bool) $new_value; + } + return $return; } } diff --git a/core/modules/entity/lib/Drupal/entity/StorableInterface.php b/core/modules/entity/lib/Drupal/entity/StorableInterface.php index 23bc1ea..b784260 100644 --- a/core/modules/entity/lib/Drupal/entity/StorableInterface.php +++ b/core/modules/entity/lib/Drupal/entity/StorableInterface.php @@ -211,8 +211,13 @@ public function getRevisionId(); /** * Checks if this entity is the current revision. * + * @param bool $new_value + * (optional) A Boolean to (re)set the isCurrentRevision flag. + * * @return bool - * TRUE if the entity is the current revision, FALSE otherwise. + * TRUE if the entity is the current revision, FALSE otherwise. If + * $new_value was passed, the previous value is returned. */ - public function isCurrentRevision(); + public function isCurrentRevision($new_value = NULL); + } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php index b96049e..99fd422 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php @@ -7,12 +7,14 @@ namespace Drupal\search\Tests; -use Drupal\simpletest\UnitTestBase; +use Drupal\simpletest\WebTestBase; /** * Tests the search_excerpt() function. */ -class SearchExcerptTest extends UnitTestBase { +class SearchExcerptTest extends WebTestBase { + public static $modules = array('search'); + public static function getInfo() { return array( 'name' => 'Search excerpt extraction', @@ -21,11 +23,6 @@ public static function getInfo() { ); } - function setUp() { - drupal_load('module', 'search'); - parent::setUp(); - } - /** * Tests search_excerpt() with several simulated search keywords. * diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 9654f9c..253d4df 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -726,6 +726,10 @@ protected function prepareEnvironment() { $GLOBALS['config_directories'][$type] = 'simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type; } + // Reset and create a new service container. + drupal_container(NULL, TRUE); + $this->container = drupal_container(); + $this->configDirectories = array(); include_once DRUPAL_ROOT . '/core/includes/install.inc'; foreach ($GLOBALS['config_directories'] as $type => $path) { diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 486afba..dc8821f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -724,6 +724,8 @@ function system_schema() { ); $schema['cache_bootstrap'] = $schema['cache']; $schema['cache_bootstrap']['description'] = 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.'; + $schema['cache_config'] = $schema['cache']; + $schema['cache_config']['description'] = 'Cache table for configuration data.'; $schema['cache_form'] = $schema['cache']; $schema['cache_form']['description'] = 'Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.'; $schema['cache_page'] = $schema['cache']; @@ -733,28 +735,6 @@ function system_schema() { $schema['cache_path'] = $schema['cache']; $schema['cache_path']['description'] = 'Cache table for path alias lookup.'; - $schema['config'] = array( - 'description' => 'Default active store for the configuration system.', - 'fields' => array( - 'name' => array( - 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'data' => array( - 'description' => 'The raw data for this configuration entry.', - 'type' => 'blob', - 'not null' => TRUE, - 'size' => 'big', - 'translatable' => TRUE, - ), - ), - 'primary key' => array('name'), - ); - - $schema['date_format_type'] = array( 'description' => 'Stores configured date format types.', 'fields' => array( @@ -1567,33 +1547,63 @@ function system_update_8002() { } /** - * Adds {config} table for new Configuration system. + * Creates {cache_config} cache table for the new configuration system. */ function system_update_8003() { - // @todo Temporary. - if (db_table_exists('config')) { - db_drop_table('config'); - } - db_create_table('config', array( - 'description' => 'Default active store for the configuration system.', + $schema['cache'] = array( + 'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.', 'fields' => array( - 'name' => array( - 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).', + 'cid' => array( + 'description' => 'Primary Key: Unique cache ID.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', ), 'data' => array( - 'description' => 'The raw data for this configuration entry.', + 'description' => 'A collection of data to cache.', 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'description' => 'A Unix timestamp indicating when the cache entry was created.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'serialized' => array( + 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', + 'type' => 'int', + 'size' => 'small', 'not null' => TRUE, + 'default' => 0, + ), + 'tags' => array( + 'description' => 'Space-separated list of cache tags for this entry.', + 'type' => 'text', 'size' => 'big', - 'translatable' => TRUE, + 'not null' => FALSE, + ), + 'checksum' => array( + 'description' => 'The tag invalidation sum when this entry was saved.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, ), ), - 'primary key' => array('name'), - )); + 'indexes' => array( + 'expire' => array('expire'), + ), + 'primary key' => array('cid'), + ); + db_create_table('cache_config', $schema['cache']); } /** @@ -1673,7 +1683,7 @@ function system_update_8007() { foreach ($tables as $table) { // Assume we have a valid cache table if there is both 'cid' and 'data' // columns. - if (db_field_exists($table, 'cid') && db_field_exists($table, 'data')) { + if (db_field_exists($table, 'cid') && db_field_exists($table, 'data') && !db_field_exists($table, 'tags')) { db_add_field($table, 'tags', array( 'description' => 'Space-separated list of cache tags for this entry.', 'type' => 'text', diff --git a/core/modules/system/system.module b/core/modules/system/system.module index f78f123..3efffd0 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -3506,7 +3506,7 @@ function system_cron() { */ function system_cache_flush() { // Do NOT flush the 'form' cache bin to retain in-progress form submissions. - return array('bootstrap', 'cache', 'page', 'path'); + return array('bootstrap', 'config', 'cache', 'page', 'path'); } /** diff --git a/core/profiles/standard/standard.info b/core/profiles/standard/standard.info index 8b8a33b..c5240dd 100644 --- a/core/profiles/standard/standard.info +++ b/core/profiles/standard/standard.info @@ -6,6 +6,7 @@ dependencies[] = node dependencies[] = block dependencies[] = color dependencies[] = comment +dependencies[] = config dependencies[] = contextual dependencies[] = help dependencies[] = image