diff --git a/core/includes/config.inc b/core/includes/config.inc index 78d12e8..8e3a109 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -1,6 +1,15 @@ delete(); + } + else { + // Get the active store object, set the new data from file, then save. + $target_config = config($name); + $source_config = config($name, 'Drupal\Core\Config\FileStorage'); + $target_config->setData($source_config->get()); + $target_config->save(); + } + } + } +} + +/** + * Invokes hook_config_sync_validate() and hook_config_sync() implementations. + * + * @param $config_changes + * An array of changes to be loaded. + */ +function config_sync_invoke_sync_hooks($config_changes) { + // Keep a copy of the changes so that a module can't modify the values by + // taking the array by reference. + $config_changes_copy = $config_changes; + + // @todo Lock writes to ALL config storages to prevent other/unintended config + // changes from happening during the import. + + $target_storage = config(NULL, 'Drupal\Core\Config\DatabaseStorage'); + $source_storage = config(NULL, 'Drupal\Core\Config\FileStorage'); + + foreach (config_sync_sort_dependencies(module_implements('config_sync_validate')) as $module) { + $config_changes = $config_changes_copy; + $function = $module . '_config_sync_validate'; + $function($config_changes, $target_storage, $source_storage); + } + + // We allow modules to signal that they would like to be rerun after all + // other modules by returning CONFIG_DEFER_SYNC. Loop until there are no + // modules left that indicate they would like to be rerun, checking that we're + // not stuck rerunning the same list of modules over and over at each cycle. + $modules = config_sync_sort_dependencies(module_implements('config_sync')); + do { + // Prevent an infinite loop. If the two variables stay the same, then all + // remaining modules asked to defer their import operations, which means + // that there is a unmet dependency. + $initial_module_list = $modules; + + foreach ($modules as $key => $module) { + $config_changes = $config_changes_copy; + $function = $module . '_config_sync'; + if ($function($config_changes, $target_storage, $source_storage) !== CONFIG_DEFER_SYNC) { + unset($modules[$key]); + } + } + } while ($modules && $modules != $initial_module_list); + + // If there are modules left that haven't run their sync hook, then we hit + // an infinite loop. + if ($modules) { + throw new ConfigException("Dependency loop detected while sync configuration from disk."); + } +} + +/** + * Invokes hook_config_sync_error() implementations. + * + * During a sync run, modules may make changes that cannot be rolled back. + * This hook allows modules to react to an error that occurs after they have + * made such changes, and make sure that the state of configuration in the + * active store is correct. + * + * @param $config_changes + * An array of changes to be loaded. + */ +function config_sync_invoke_sync_error_hooks($config_changes) { + $target_storage = config(NULL, 'Drupal\Core\Config\DatabaseStorage'); + $source_storage = config(NULL, 'Drupal\Core\Config\FileStorage'); + + $modules = config_sync_sort_dependencies(module_implements('config_sync_error')); + foreach ($modules as $module) { + $function = $module . '_config_sync_error'; + try { + $function($config_changes, $target_storage, $source_storage); + } + catch (ConfigException $e) { + // Just keep going, because we need to allow all modules to react even if + // some of them are behaving badly. + } + } +} + +/** + * 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_sync_sort_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 disk compared to the active store. + * + * @return + * The list of files changed on disk compared to the active store. + */ +function config_get_changes_from_disk() { + $disk_config_names = FileStorage::getNamesWithPrefix(); + $active_config_names = DatabaseStorage::getNamesWithPrefix(); + $config_changes = array( + 'new' => array_diff($disk_config_names, $active_config_names), + 'changed' => array(), + 'deleted' => array_diff($active_config_names, $disk_config_names), + ); + foreach (array_intersect($disk_config_names, $active_config_names) as $name) { + $active_config = config($name); + $file_config = config($name, 'Drupal\Core\Config\FileStorage'); + if ($active_config->get() != $file_config->get()) { + $config_changes['changed'][] = $name; + } + } + if (empty($config_changes['new']) && empty($config_changes['changed']) && empty($config_changes['deleted'])) { + return FALSE; + } + return $config_changes; +} + diff --git a/core/includes/module.inc b/core/includes/module.inc index 6b4604a..007a068 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -484,7 +484,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/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index 63ae7b4..31d2c8d 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -11,7 +11,7 @@ use Exception; class DatabaseStorage extends StorageBase { /** - * Overrides StorageBase::read(). + * Implements StorageInterface::read(). */ public function read() { // There are situations, like in the installer, where we may attempt a diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/DrupalConfig.php index c405757..59cfc00 100644 --- a/core/lib/Drupal/Core/Config/DrupalConfig.php +++ b/core/lib/Drupal/Core/Config/DrupalConfig.php @@ -17,6 +17,18 @@ class DrupalConfig { */ protected $storage; + /** + * The name of the current configuration object. + * + * @var string + */ + protected $name; + + /** + * The data of the configuration object. + * + * @var array + */ protected $data = array(); /** @@ -29,7 +41,24 @@ class DrupalConfig { */ public function __construct(StorageInterface $storage) { $this->storage = $storage; + // Retrieve the configuration object name assigned to the storage + // controller and automatically load it, if any. + $this->name = $this->storage->getName(); + if (isset($this->name)) { + $this->read(); + } + } + + /** + * Loads a configuration object. + * + * @param string $name + * The configuration object name to load. + */ + public function load($name) { + $this->storage->setName($name); $this->read(); + return $this; } /** @@ -113,6 +142,11 @@ class DrupalConfig { } } + public function setData(array $data) { + $this->data = $data; + return $this; + } + /** * Sets value in this config object. * @@ -195,6 +229,7 @@ class DrupalConfig { else { drupal_array_unset_nested_value($this->data, $parts); } + return $this; } /** @@ -202,6 +237,7 @@ class DrupalConfig { */ public function save() { $this->storage->write(config_encode($this->data)); + return $this; } /** @@ -210,5 +246,6 @@ class DrupalConfig { public function delete() { $this->data = array(); $this->storage->delete(); + return $this; } } diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index e65e168..ab012a9 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -2,13 +2,12 @@ namespace Drupal\Core\Config; +use Drupal\Core\Config\StorageInterface; + /** - * Represents the file storage interface. - * - * Classes implementing this interface allow reading and writing configuration - * data to and from disk. + * Represents the file storage controller. */ -class FileStorage { +class FileStorage implements StorageInterface { /** * Constructs a FileStorage object. @@ -16,81 +15,125 @@ class FileStorage { * @param string $name * The name for the configuration data. Should be lowercase. */ - public function __construct($name) { + public function __construct($name = NULL) { $this->name = $name; } /** - * Reads and returns a file. - * - * @return - * The data of the file. - * - * @throws - * Exception - */ - protected function readData() { - $data = file_get_contents($this->getFilePath()); - if ($data === FALSE) { - throw new FileStorageReadException('Read file is invalid.'); - } - return $data; - } - - /** - * Checks whether the XML configuration file already exists on disk. + * Returns whether the configuration file exists. * - * @return - * @todo + * @return bool + * TRUE if the configuration file exists, FALSE otherwise. */ protected function exists() { return file_exists($this->getFilePath()); } /** - * Returns the path to the XML configuration file. + * Returns the path to the configuration file. * - * @return - * @todo + * @return string + * The path to the configuration file. */ public function getFilePath() { + // @todo Cache this? 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. + * Implements StorageInterface::read(). * - * @throws - * Exception + * @throws FileStorageReadException */ - public function write($data) { - if (!file_put_contents($this->getFilePath(), $data)) { - throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath()); + public function read() { + if ($this->exists()) { + $data = file_get_contents($this->getFilePath()); + if ($data === FALSE) { + throw new FileStorageReadException('Failed to read configuration file: ' . $this->getFilePath()); + } + return $data; } + return FALSE; } /** - * Returns the contents of the configuration file. + * Implements StorageInterface::write(). * - * @return - * @todo + * @throws FileStorageException */ - public function read() { - if ($this->exists()) { - $data = $this->readData(); - return $data; + public function write($data) { + if (!file_put_contents($this->getFilePath(), $data)) { + throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath()); } - return FALSE; } /** * Deletes a configuration file. */ public function delete() { - // Needs error handling and etc. - @drupal_unlink($this->getFilePath()); + // @todo Error handling. + return @drupal_unlink($this->getFilePath()); + } + + /** + * Implements StorageInterface::copyToFile(). + */ + public function copyToFile() { + // @todo Untangle StorageInterface. + } + + /** + * Implements StorageInterface::copyFromFile(). + */ + public function copyFromFile() { + // @todo Untangle StorageInterface. + } + + /** + * Implements StorageInterface::deleteFile(). + */ + public function deleteFile() { + // @todo Untangle StorageInterface. + return $this->delete(); + } + + /** + * Implements StorageInterface::writeToActive(). + */ + public function writeToActive($data) { + // @todo Untangle StorageInterface. + } + + /** + * Implements StorageInterface::writeToFile(). + */ + public function writeToFile($data) { + // @todo Untangle StorageInterface. + return $this->write($data); + } + + /** + * Implements StorageInterface::getName(). + */ + public function getName() { + return $this->name; + } + + /** + * Implements StorageInterface::setName(). + */ + public function setName($name) { + $this->name = $name; + } + + /** + * Implements StorageInterface::getNamesWithPrefix(). + */ + public static function getNamesWithPrefix($prefix = '') { + $files = glob(config_get_config_directory() . '/' . $prefix . '*.xml'); + $clean_name = function ($value) { + return basename($value, '.xml'); + }; + return array_map($clean_name, $files); } } diff --git a/core/lib/Drupal/Core/Config/StorageBase.php b/core/lib/Drupal/Core/Config/StorageBase.php index 7846aed..f521332 100644 --- a/core/lib/Drupal/Core/Config/StorageBase.php +++ b/core/lib/Drupal/Core/Config/StorageBase.php @@ -6,10 +6,15 @@ use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\FileStorage; /** - * @todo + * Base class for configuration storage controllers. */ abstract class StorageBase implements StorageInterface { + /** + * The name of the configuration object. + * + * @var string + */ protected $name; /** @@ -22,7 +27,7 @@ abstract class StorageBase implements StorageInterface { /** * Implements StorageInterface::__construct(). */ - function __construct($name) { + function __construct($name = NULL) { $this->name = $name; } @@ -71,13 +76,6 @@ abstract class StorageBase implements StorageInterface { } /** - * Implements StorageInterface::isOutOfSync(). - */ - public function isOutOfSync() { - return $this->read() !== $this->readFromFile(); - } - - /** * Implements StorageInterface::write(). */ public function write($data) { @@ -106,4 +104,11 @@ abstract class StorageBase implements StorageInterface { public function getName() { return $this->name; } + + /** + * Implements StorageInterface::setName(). + */ + public function setName($name) { + $this->name = $name; + } } diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index f1e8a3d..0d287dd 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -5,18 +5,20 @@ namespace Drupal\Core\Config; /** * Defines an interface for configuration storage manipulation. * - * This class allows reading and writing configuration data from/to the - * storage and copying to/from the file storing the same data. + * Classes implementing this interface allow reading and writing configuration + * data from and to the storage. + * + * @todo Remove all active/file methods. They belong onto DrupalConfig only. */ interface StorageInterface { /** * Constructs a storage manipulation class. * - * @param $name - * Lowercase string, the name for the configuration data. + * @param string $name + * (optional) The name for the configuration object to read. */ - function __construct($name); + function __construct($name = NULL); /** * Reads the configuration data from the storage. @@ -39,15 +41,6 @@ interface StorageInterface { function deleteFile(); /** - * Checks whether the file and the storage is in sync. - * - * @return - * TRUE if the file and the storage contains the same data, FALSE - * if not. - */ - function isOutOfSync(); - - /** * Writes the configuration data into the active storage and the file. * * @param $data @@ -75,15 +68,31 @@ interface StorageInterface { function writeToFile($data); /** - * Gets names starting with this prefix. - * - * @param $prefix - * @todo + * Gets the name of this object. */ - static function getNamesWithPrefix($prefix); + public function getName(); /** - * Gets the name of this object. + * Sets the name of this object. */ - public function getName(); + public function setName($name); + + /** + * Gets configuration object names starting with a given prefix. + * + * Given the following configuration objects: + * - node.type.article + * - node.type.page + * + * Passing the prefix 'node.type' will return an array containing the above + * names. + * + * @param string $prefix + * (optional) The prefix to search for. If omitted, all configuration object + * names that exist are returned. + * + * @return array + * An array containing matching configuration object names. + */ + static function getNamesWithPrefix($prefix = ''); } diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc new file mode 100644 index 0000000..1169bc0 --- /dev/null +++ b/core/modules/config/config.admin.inc @@ -0,0 +1,58 @@ + 'There are no changes on disk to reload.' + ); + 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['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_sync()) { + drupal_set_message('Configuration successfully reloaded from disk.'); + } + else { + drupal_set_message('There was an error reloading configuration from disk.', 'error'); + } +} + diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php new file mode 100644 index 0000000..8564502 --- /dev/null +++ b/core/modules/config/config.api.php @@ -0,0 +1,35 @@ + t('Import configuration'), + 'restrict access' => TRUE, + ); + return $permissions; +} + +/** + * Implements hook_menu(). + */ +function config_menu() { + $items['admin/config/development/import'] = array( + 'title' => 'Import configuration', + 'description' => 'Import and synchronize configuration changes.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('config_admin_import_form'), + 'access arguments' => array('import configuration'), + 'file' => 'config.admin.inc', + ); + return $items; +} + diff --git a/core/modules/config/config.test b/core/modules/config/config.test index f69dde9..023819d 100644 --- a/core/modules/config/config.test +++ b/core/modules/config/config.test @@ -198,22 +198,22 @@ class ConfigFileContentTestCase extends WebTestBase { // Get file listing for all files starting with 'foo'. Should return // two elements. - $files = config_get_files_with_prefix('foo'); + $files = FileStorage::getNamesWithPrefix('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 = config_get_files_with_prefix('biff'); + $files = FileStorage::getNamesWithPrefix('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 = config_get_files_with_prefix('foo.bar'); + $files = FileStorage::getNamesWithPrefix('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 = config_get_files_with_prefix('bar'); + $files = FileStorage::getNamesWithPrefix('bar'); $this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.'); // Delete the configuration. @@ -229,6 +229,126 @@ class ConfigFileContentTestCase extends WebTestBase { } } +/** + * Tests config_sync() functionality. + */ +class ConfigReloadTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Import configuration', + 'description' => 'Tests importing configuration from files into active store.', + 'group' => 'Configuration', + ); + } + + public function setUp() { + parent::setUp('config_test'); + } + + public function testDeletedConfigFile() { + // Check a value in the active store for config_test. + $config = config('config_test.system'); + $this->assertTrue($config->get('foo') == 'bar', 'The config_test.system.xml file has been loaded into the active store.'); + // Delete one of the config_test files. + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + unlink($config_file_path); + // Run config_sync(). + config_sync(); + // Check that the value has disappeared. + $config = config('config_test.system'); + $this->assertTrue($config->get('foo') === NULL, 'The config_test.system.xml file values has been removed from the active store.'); + } + + public function testNewConfigFile() { + // Create a new file. + $xml =<< + + new value + +XML; + $new_config_file_path = config_get_config_directory() . '/config_test.new.xml'; + file_put_contents($new_config_file_path, $xml); + // Run config_sync(). + config_sync(); + // Check that the value has appeared. + $config = config('config_test.new'); + $this->assertTrue($config->get('add_me') == 'new value', 'A new config file was loaded into the active store by config_sync().'); + } + + public function testUpdatedConfigFile() { + // Update a config_test config file in-place, check that the value does not + // appear in the active store. + $xml =<< + + not bar + +XML; + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + file_put_contents($config_file_path, $xml); + $config = config('config_test.system'); + $this->assertTrue($config->get('foo') == 'bar', 'The config_test.system:foo value has not changed.'); + // Run config_sync(). + config_sync(); + // Check that the updated value has appeared. + $config = config('config_test.system'); + $this->assertTrue($config->get('foo') == 'not bar', 'The config_test.system:foo value has been updated by the config_sync() run.'); + } + + public function testReloadHook() { + // Delete a file so that hook_config_sync() hooks are run. + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + unlink($config_file_path); + // Run config_sync(). + config_sync(); + // Check that we get called when config_sync() is run. + $config_sync_hook_called = isset($GLOBALS['hook_config_sync']) && $GLOBALS['hook_config_sync'] == 'config_test_config_sync'; + $this->assertTrue($config_sync_hook_called, "The config_test module's hook_config_sync() implementation was called."); + } + + public function testReloadErrorHook() { + // Delete a file so that hook_config_sync() hooks are run. + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + unlink($config_file_path); + // Enable the module that will blow up during a config_sync() or set a + // global or something. + $GLOBALS['config_sync_throw_error'] = TRUE; + // Run config_sync(). + config_sync(); + // Check that we get called when config_sync() is run and the explosion + // happens. + $config_sync_error_hook_called = isset($GLOBALS['hook_config_sync_error']) && $GLOBALS['hook_config_sync_error'] == 'config_test_config_sync_error'; + $this->assertTrue($config_sync_error_hook_called, "The config_test module's hook_config_sync_error() implementation was called."); + } + + public function testReloadValidateHook() { + // Delete a file so that hook_config_sync() hooks are run. + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + unlink($config_file_path); + // Run config_sync(). + config_sync(); + // Check that we get called when config_sync() is run. + $config_sync_validate_hook_called = isset($GLOBALS['hook_config_sync_validate']) && $GLOBALS['hook_config_sync_validate'] == 'config_test_config_sync_validate'; + $this->assertTrue($config_sync_validate_hook_called, "The config_test module's hook_config_sync_validate() implementation was called."); + } + + public function testReloadValidateHookThrowsError() { + // Delete a file so that hook_config_sync() hooks are run. + $config_file_path = config_get_config_directory() . '/config_test.system.xml'; + unlink($config_file_path); + // Enable the module that will blow up during a hook_config_sync_validate() or set a + // global or something. + $GLOBALS['config_sync_validate_throw_error'] = TRUE; + // Run config_sync(). + config_sync(); + // Check that the config reload run didn't update the active store. + $config = config('config_test.system'); + $this->assertTrue($config->get('foo') == 'bar', 'The config_test.system:foo value has not changed.'); + } +} + /** * Tests configuration overriding from settings.php. */ diff --git a/core/modules/config/config_test/config/config_test.delete.xml b/core/modules/config/config_test/config/config_test.delete.xml new file mode 100644 index 0000000..4ec808f --- /dev/null +++ b/core/modules/config/config_test/config/config_test.delete.xml @@ -0,0 +1,4 @@ + + + bar + diff --git a/core/modules/config/config_test/config/config_test.system.xml b/core/modules/config/config_test/config/config_test.system.xml new file mode 100644 index 0000000..6ea745f --- /dev/null +++ b/core/modules/config/config_test/config/config_test.system.xml @@ -0,0 +1,4 @@ + + + bar + diff --git a/core/modules/config/config_test/config_test.info b/core/modules/config/config_test/config_test.info new file mode 100644 index 0000000..8735450 --- /dev/null +++ b/core/modules/config/config_test/config_test.info @@ -0,0 +1,6 @@ +name = Configuration test module +package = Core +version = VERSION +core = 8.x +dependencies[] = config +hidden = TRUE diff --git a/core/modules/config/config_test/config_test.module b/core/modules/config/config_test/config_test.module new file mode 100644 index 0000000..cfe337a --- /dev/null +++ b/core/modules/config/config_test/config_test.module @@ -0,0 +1,36 @@ +load($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.style.') === 0) { + $style = $source_storage->load($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.style.') === 0) { + // The style has been deleted, so read the previous configuration from the + // old storage. + $style = $target_storage->load($file_name)->get(); + image_style_flush($style); + + // @todo image_style_delete() supports the notion of a "replacement style" + // to be used by other modules instead of the deleted style. Good idea. + // But squeezing that into a "delete" operation is the worst idea ever. + // Regardless of Image module insanity, add a 'replaced' stack to + // config_sync()? And how can that work? If an 'old_ID' key would be a + // standard, wouldn't this belong into 'changed' instead? + $style['old_name'] = $style['name']; + $style['name'] = ''; + module_invoke_all('image_style_delete', $style); + } + } +} + +/** * Get an array of all styles and their settings. * * @return