diff --git a/core/includes/config.inc b/core/includes/config.inc index 8c2772b..2252583 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -1,6 +1,13 @@ "); + $xml_object = new SimpleXMLElement(""); config_array_to_xml($data, $xml_object); // Pretty print the result. @@ -240,3 +250,165 @@ function config_array_to_xml($array, &$xml_object) { } } } + +/** + * Reload config from disc and write new settings to the active store. + */ +function config_reload_from_disc() { + $lock_attempts = 0; + $lock_acquired = FALSE; + do { + if ($lock_acquired = lock_acquire(__FUNCTION__, 2)) { + // Find out what has changed, and pass the keys for new, changed and + // deleted config objects to modules. + $config_changes = config_get_changes_from_disc(); + + // Only proceed if there are changes. + if (empty($config_changes['new']) && empty($config_changes['changed']) && empty($config_changes['deleted'])) { + lock_release(__FUNCTION__); + return; + } + + try { + // Let modules react before we overrwrite the active store, so that they + // can compare old values (active store) to new values (disc), and act + // accordingly. + $active_config_tree = config_tree(); + $file_config_tree = config_tree('Drupal\Core\Config\DrupalConfigFile'); + $modules = config_sort_module_reload_dependencies(module_implements('config_reload_from_disc')); + do { + // Keep a copy, so we can check that we're not in a loop. + $initial_module_list = $modules; + $modules = config_run_reload_hooks($config_changes, $active_config_tree, $file_config_tree, $modules); + } while ($modules && $modules != $initial_module_list); + + if ($modules) { + // We got stuck in a loop processing this reload, bail. + throw new Exception("Dependency loop detected while reloading configuration from disc."); + } + + // Bring the changes from disc into the active store. + foreach (array('new', 'changed', 'deleted') as $type) { + foreach ($config_changes[$type] as $name) { + if ($type == 'deleted') { + config($name)->delete(); + } + else { + // Get the active store object, set the new data from file, then + // save, which will also update the .sig file. + $active_store_config = config($name); + $active_store_config->setData(config($name, 'Drupal\Core\Config\DrupalConfigFile')->get()); + $active_store_config->save(); + } + } + } + lock_release(__FUNCTION__); + } + catch (Exception $e) { + lock_release(__FUNCTION__); + throw new Exception("An error occurred reloading from disc: " . $e->getMessage()); + } + } + else { + lock_wait(__FUNCTION__, 2); + } + } while ($lock_acquired === FALSE && ++$lock_attempts < 5); + // Oh noes, we failed after 5 attempts, watchdog()? Throw an exception? +} + +/** + * Runs hook_config_reload implementations. + * + * @param $config_changes + * An array of changes to be loaded. + * @param $active_config_tree + * A ConfigTree object pointing at the active store. + * @param $file_config_tree + * A ConfigTree object pointing at the disc. + * @return + * A list of modules to run again for this reload. + */ +function config_run_reload_hooks($config_changes, $active_config_tree, $file_config_tree, $modules) { + $modules_to_rerun = array(); + foreach ($modules as $module) { + $function = $module . '_config_reload_from_disc'; + if ($function($config_changes, $file_config_tree, $active_config_tree) === CONFIG_DEFER_RELOAD) { + $modules_to_rerun[] = $module; + } + } + return $modules_to_rerun; +} + +/** + * Load all the config names! From disc. + */ +function config_get_names_from_disc() { + $config_names = array(); + foreach (glob(config_get_config_directory() . '/' . '*.xml') as $key => $file) { + $parts = explode('/', $file); + $file = array_pop($parts); + $name = str_replace('.xml', '', $file); + $config_names[] = $name; + } + return $config_names; +} + +/** + * Returns a DrupalConfigTree object with the given storage backend. + * + * @param $storage_class + * A storage class. + * @return + * A DrupalConfigTree object. + */ +function config_tree($storage_class = NULL) { + if ($storage_class === NULL) { + $storage_class = variable_get('config_default_storage', 'Drupal\Core\Config\DrupalVerifiedStorageSQL'); + } + return new DrupalConfigTree($storage_class); +} + +/** + * Sort the given list of modules based on dependency. + * + * @param $modules + * A list of modules. + * @return + * The list of modules sorted by dependency. + */ +function config_sort_module_reload_dependencies($modules) { + // Get all module data so we can find find weights and sort. + $module_data = system_rebuild_module_data(); + + $sorted_modules = array(); + foreach ($modules as $module) { + $sorted_modules[$module] = $module_data[$module]->sort; + } + arsort($sorted_modules); + return array_keys($sorted_modules); +} + +/** + * Returns a list of changes on disc compared to the active store. + * + * @return + * The list of files changed on disc compared to the active store. + */ +function config_get_changes_from_disc() { + $disc_config_names = config_get_names_from_disc(); + $active_config_names = config_get_verified_storage_names_with_prefix(); + $config_changes = array( + 'new' => array_diff($disc_config_names, $active_config_names), + 'changed' => array(), + 'deleted' => array_diff($active_config_names, $disc_config_names), + ); + foreach (array_intersect($disc_config_names, $active_config_names) as $name) { + $active_config = config($name); + $file_config = config($name, 'Drupal\Core\Config\DrupalConfigFile'); + if ($active_config->get() != $file_config->get()) { + $config_changes['changed'][] = $name; + } + } + return $config_changes; +} + diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 493246c..8bdd116 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1034,7 +1034,7 @@ function install_settings_form_submit($form, &$form_state) { $config_path = conf_path() . '/files/' . $settings['config_directory_name']['value']; if (!file_prepare_directory($config_path, FILE_CREATE_DIRECTORY)) { // How best to handle errors here? - }; + } // Write out a .htaccess file that will protect the config directory from // prying eyes. diff --git a/core/includes/module.inc b/core/includes/module.inc index aa9eaf5..7cdc2d3 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -461,7 +461,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) { $versions = drupal_get_schema_versions($module); $version = $versions ? max($versions) : SCHEMA_INSTALLED; - // Copy any default configuration data to the system config directory/ + // Copy any default configuration data to the active store. config_install_default_config($module); // If the module has no current updates, but has some that were diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/DrupalConfig.php index 54397e7..6207daa 100644 --- a/core/lib/Drupal/Core/Config/DrupalConfig.php +++ b/core/lib/Drupal/Core/Config/DrupalConfig.php @@ -97,6 +97,10 @@ class DrupalConfig { } } + public function setData(array $data) { + $this->data = $data; + } + /** * Sets value in this config object. * diff --git a/core/lib/Drupal/Core/Config/DrupalConfigFile.php b/core/lib/Drupal/Core/Config/DrupalConfigFile.php new file mode 100644 index 0000000..763ee7d --- /dev/null +++ b/core/lib/Drupal/Core/Config/DrupalConfigFile.php @@ -0,0 +1,144 @@ +name = $name; + } + + /** + * Checks whether the XML configuration file already exists on disk. + * + * @return + * Boolean based on file's existence. + */ + protected function exists() { + return file_exists($this->getFilePath()); + } + + /** + * Returns the path to the XML configuration file. + * + * @return + * @todo + */ + public function getFilePath() { + return config_get_config_directory() . '/' . $this->name . '.xml'; + } + + /** + * Writes the contents of the configuration file to disk. + * + * @param $data + * The data to be written to the file. + * + * @throws + * Exception + */ + public function write($data) { + if (!file_put_contents($this->getFilePath(), $data)) { + throw new \Exception('Failed to write configuration file: ' . $this->getFilePath()); + } + } + + /** + * Returns the contents of the configuration file. + * + * @return + * @todo + */ + public function read() { + if ($this->exists()) { + return file_get_contents($this->getFilePath()); + } + throw new \Exception('Failed to read configuration file: ' . $this->getFilePath()); + } + + /** + * Deletes a configuration file. + */ + public function delete() { + return @drupal_unlink($this->getFilePath()); + } + + /** + * Copies the configuration data from the verified storage into a file. + */ + public function copyToFile() { + // TODO: no-op to keep the interface happy. + } + + /** + * Copies the configuration data from the file into the verified storage. + */ + public function copyFromFile() { + // TODO: no-op to keep the interface happy. + } + + /** + * Deletes the configuration data file. + */ + public function deleteFile() { + return $this->delete(); + } + + /** + * Checks whether the file and the verified storage is in sync. + * + * @return + * TRUE if the file and the verified storage contains the same data, FALSE + * if not. + */ + public function isOutOfSync() { + return FALSE; + } + + /** + * Writes the configuration data into the active storage but not the file. + * + * @param $data + * The configuration data to write into active storage. + */ + public function writeToActive($data) { + // TODO: no-op to keep the interface happy. + } + + /** + * Writes the configuration data into the file. + * + * @param $data + * The configuration data to write into the file. + */ + public function writeToFile($data) { + return $this->write($data); + } + + /** + * Gets names starting with this prefix. + * + * @param $prefix + * The prefix of the files we are searching for. + * + * @return + * An array of file names under a branch. + * + * @see config_get_signed_file_storage_names_with_prefix() + */ + public static function getNamesWithPrefix($prefix) { + return config_get_signed_file_storage_names_with_prefix($prefix); + } +} + diff --git a/core/lib/Drupal/Core/Config/DrupalConfigTree.php b/core/lib/Drupal/Core/Config/DrupalConfigTree.php new file mode 100644 index 0000000..0d551ac --- /dev/null +++ b/core/lib/Drupal/Core/Config/DrupalConfigTree.php @@ -0,0 +1,18 @@ +storage_class = $storage_class; + } + + public function get($name) { + return config($name, $this->storage_class); + } + +} + diff --git a/core/modules/image/image.module b/core/modules/image/image.module index bcccbf7..6005b86 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -527,6 +527,36 @@ function image_style_save($style) { } /** + * Implements hook_config_reload_from_disc(). + */ +function image_config_reload_from_disc($config_changes, $files_config_tree, $active_config_tree) { + foreach ($config_changes['new'] as $file_name) { + if (strpos($file_name, 'image.styles.') === 0) { + $style = $files_config_tree->get($file_name)->get(); + $style['is_new'] = TRUE; + module_invoke_all('image_style_save', $style); + image_style_flush($style); + } + } + foreach ($config_changes['changed'] as $file_name) { + if (strpos($file_name, 'image.styles.') === 0) { + $style = $files_config_tree->get($file_name)->get(); + $style['is_new'] = FALSE; + module_invoke_all('image_style_save', $style); + image_style_flush($style); + } + } + foreach ($config_changes['deleted'] as $file_name) { + if (strpos($file_name, 'image.styles.') === 0) { + image_style_flush($style); + $style['old_name'] = $style['name']; + $style['name'] = ''; + module_invoke_all('image_style_delete', $style); + } + } +} + +/** * Delete an image style. * * @param $style diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index a45a0e3..c096b67 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -3202,3 +3202,37 @@ function system_actions_remove_orphans() { actions_synchronize(TRUE); drupal_goto('admin/config/system/actions/manage'); } + +/** + * Reload config from disc form. + */ +function system_admin_config_reload_form($form, &$form_state) { + $config_changes = config_get_changes_from_disc(); + if ($config_changes['new'] || $config_changes['changed'] || $config_changes['deleted']) { + return array( + 'changed_files' => array( + '#markup' => '
' . print_r($config_changes, TRUE) . '
', + ), + 'reload' => array( + '#type' => 'submit', + '#value' => 'Reload config from disc', + ), + ); + } + else { + return array( + 'no_changes' => array( + '#markup' => 'There are no changes on disc to reload.' + ), + ); + } +} + +/** + * Reload config from disc form submit handler. + */ +function system_admin_config_reload_form_submit($form, &$form_state) { + config_reload_from_disc(); + drupal_set_message('Configuration successfully reloaded from disc.'); +} + diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 469f996..fd2e00c 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -745,6 +745,18 @@ function system_menu() { 'file' => 'system.admin.inc', ); + // Config system reload. + $items['admin/config/config/reload'] = array( + 'title' => 'Configuration reload', + 'description' => 'Reload configuration from disc.', + 'position' => 'left', + 'weight' => -10, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_admin_config_reload_form'), + 'access arguments' => array('reload configuration from disc'), + 'file' => 'system.admin.inc', + ); + // Media settings. $items['admin/config/media'] = array( 'title' => 'Media',