From 65d79d4c9350665fedbf434799fed335de64688e Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 3 Jan 2023 14:18:53 +0100 Subject: [PATCH] Introduce ISetConfigValuesTransactional for transactional config behaviour --- src/Console/Maintenance.php | 10 +- src/Console/Relocate.php | 7 +- .../Config/Capability/IManageConfigValues.php | 29 ++- .../ISetConfigValuesTransactional.php | 84 +++++++++ src/Core/Config/Model/Config.php | 43 +++-- src/Core/Config/Model/TransactionalConfig.php | 89 +++++++++ src/Core/Config/ValueObject/Cache.php | 67 +++++++ src/Core/Update.php | 28 +-- src/Database/DBStructure.php | 7 +- src/Module/Admin/Site.php | 176 +++++++++--------- tests/src/Core/Config/Cache/CacheTest.php | 48 +++++ .../Config/Cache/ConfigFileLoaderTest.php | 34 ++-- .../Core/Config/TransactionalConfigTest.php | 110 +++++++++++ update.php | 6 +- 14 files changed, 588 insertions(+), 150 deletions(-) create mode 100644 src/Core/Config/Capability/ISetConfigValuesTransactional.php create mode 100644 src/Core/Config/Model/TransactionalConfig.php create mode 100644 tests/src/Core/Config/TransactionalConfigTest.php diff --git a/src/Console/Maintenance.php b/src/Console/Maintenance.php index bd3aef7c2..6a11eb2bb 100644 --- a/src/Console/Maintenance.php +++ b/src/Console/Maintenance.php @@ -100,17 +100,19 @@ HELP; $enabled = intval($this->getArgument(0)); - $this->config->set('system', 'maintenance', $enabled, false); + $transactionConfig = $this->config->transactional(); + + $transactionConfig->set('system', 'maintenance', $enabled); $reason = $this->getArgument(1); if ($enabled && $this->getArgument(1)) { - $this->config->set('system', 'maintenance_reason', $this->getArgument(1), false); + $transactionConfig->set('system', 'maintenance_reason', $this->getArgument(1)); } else { - $this->config->set('system', 'maintenance_reason', '', false); + $transactionConfig->delete('system', 'maintenance_reason'); } - $this->config->save(); + $transactionConfig->save(); if ($enabled) { $mode_str = "maintenance mode"; diff --git a/src/Console/Relocate.php b/src/Console/Relocate.php index 8a76c9207..7a2ef1d07 100644 --- a/src/Console/Relocate.php +++ b/src/Console/Relocate.php @@ -189,9 +189,10 @@ HELP; return 1; } finally { $this->out('Leaving maintenance mode'); - $this->config->set('system', 'maintenance', false, false); - $this->config->set('system', 'maintenance_reason', '', false); - $this->config->save(); + $this->config->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); } // send relocate diff --git a/src/Core/Config/Capability/IManageConfigValues.php b/src/Core/Config/Capability/IManageConfigValues.php index 88fa96314..42ebea000 100644 --- a/src/Core/Config/Capability/IManageConfigValues.php +++ b/src/Core/Config/Capability/IManageConfigValues.php @@ -22,6 +22,7 @@ namespace Friendica\Core\Config\Capability; use Friendica\Core\Config\Exception\ConfigPersistenceException; +use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Core\Config\ValueObject\Cache; /** @@ -57,6 +58,20 @@ interface IManageConfigValues */ public function get(string $cat, string $key, $default_value = null); + /** + * Load all configuration values from a given cache and saves it back in the configuration node store + * @see ConfigFileManager::CONFIG_DATA_FILE + * + * All configuration values of the system are stored in the cache. + * + * @param Cache $cache a new cache + * + * @return void + * + * @throws ConfigPersistenceException In case the persistence layer throws errors + */ + public function load(Cache $cache); + /** * Sets a configuration value for system config * @@ -67,20 +82,21 @@ interface IManageConfigValues * @param string $cat The category of the configuration value * @param string $key The configuration key to set * @param mixed $value The value to store - * @param bool $autosave If true, implicit save the value * * @return bool Operation success * * @throws ConfigPersistenceException In case the persistence layer throws errors */ - public function set(string $cat, string $key, $value, bool $autosave = true): bool; + public function set(string $cat, string $key, $value): bool; /** - * Save back the overridden values of the config cache + * Creates a transactional config value store, which is used to set a bunch of values at once * - * @throws ConfigPersistenceException In case the persistence layer throws errors + * It relies on the current instance, so after save(), the values of this config class will get altered at once too. + * + * @return ISetConfigValuesTransactional */ - public function save(); + public function transactional(): ISetConfigValuesTransactional; /** * Deletes the given key from the system configuration. @@ -89,14 +105,13 @@ interface IManageConfigValues * * @param string $cat The category of the configuration value * @param string $key The configuration key to delete - * @param bool $autosave If true, implicit save the value * * @return bool * * @throws ConfigPersistenceException In case the persistence layer throws errors * */ - public function delete(string $cat, string $key, bool $autosave = true): bool; + public function delete(string $cat, string $key): bool; /** * Returns the Config Cache diff --git a/src/Core/Config/Capability/ISetConfigValuesTransactional.php b/src/Core/Config/Capability/ISetConfigValuesTransactional.php new file mode 100644 index 000000000..9c58427a0 --- /dev/null +++ b/src/Core/Config/Capability/ISetConfigValuesTransactional.php @@ -0,0 +1,84 @@ +. + * + */ + +namespace Friendica\Core\Config\Capability; + +use Friendica\Core\Config\Exception\ConfigPersistenceException; + +/** + * Interface for transactional saving of config values + * It buffers every set/delete until "save()" is called + */ +interface ISetConfigValuesTransactional +{ + /** + * Get a particular user's config variable given the category name + * ($cat) and a $key. + * + * Get a particular config value from the given category ($cat) + * + * @param string $cat The category of the configuration value + * @param string $key The configuration key to query + * + * @return mixed Stored value or null if it does not exist + * + * @throws ConfigPersistenceException In case the persistence layer throws errors + * + */ + public function get(string $cat, string $key); + + /** + * Sets a configuration value for system config + * + * Stores a config value ($value) in the category ($cat) under the key ($key) + * + * Note: Please do not store booleans - convert to 0/1 integer values! + * + * @param string $cat The category of the configuration value + * @param string $key The configuration key to set + * @param mixed $value The value to store + * + * @return static the current instance + * + * @throws ConfigPersistenceException In case the persistence layer throws errors + */ + public function set(string $cat, string $key, $value): self; + + /** + * Deletes the given key from the system configuration. + * + * @param string $cat The category of the configuration value + * @param string $key The configuration key to delete + * + * @return static the current instance + * + * @throws ConfigPersistenceException In case the persistence layer throws errors + * + */ + public function delete(string $cat, string $key): self; + + /** + * Saves the node specific config values + * + * @throws ConfigPersistenceException In case the persistence layer throws errors + */ + public function save(): void; +} diff --git a/src/Core/Config/Model/Config.php b/src/Core/Config/Model/Config.php index 7829b75ff..24f5fd3b5 100644 --- a/src/Core/Config/Model/Config.php +++ b/src/Core/Config/Model/Config.php @@ -22,6 +22,7 @@ namespace Friendica\Core\Config\Model; use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Config\Capability\ISetConfigValuesTransactional; use Friendica\Core\Config\Exception\ConfigFileException; use Friendica\Core\Config\Exception\ConfigPersistenceException; use Friendica\Core\Config\Util\ConfigFileManager; @@ -61,8 +62,17 @@ class Config implements IManageConfigValues return $this->configCache; } - /** {@inheritDoc} */ - public function save() + /** {@inheritDoc} */ + public function transactional(): ISetConfigValuesTransactional + { + return new TransactionalConfig($this); + } + + /** + * Saves the current Configuration back into the data config. + * @see ConfigFileManager::CONFIG_DATA_FILE + */ + protected function save() { try { $this->configFileManager->saveData($this->configCache); @@ -84,6 +94,13 @@ class Config implements IManageConfigValues $this->configCache = $configCache; } + /** {@inheritDoc} */ + public function load(Cache $cache) + { + $this->configCache = $cache; + $this->save(); + } + /** {@inheritDoc} */ public function get(string $cat, string $key, $default_value = null) { @@ -91,26 +108,24 @@ class Config implements IManageConfigValues } /** {@inheritDoc} */ - public function set(string $cat, string $key, $value, bool $autosave = true): bool + public function set(string $cat, string $key, $value): bool { - $stored = $this->configCache->set($cat, $key, $value, Cache::SOURCE_DATA); - - if ($stored && $autosave) { + if ($this->configCache->set($cat, $key, $value, Cache::SOURCE_DATA)) { $this->save(); + return true; + } else { + return false; } - - return $stored; } /** {@inheritDoc} */ - public function delete(string $cat, string $key, bool $autosave = true): bool + public function delete(string $cat, string $key): bool { - $removed = $this->configCache->delete($cat, $key); - - if ($removed && $autosave) { + if ($this->configCache->delete($cat, $key)) { $this->save(); + return true; + } else { + return false; } - - return $removed; } } diff --git a/src/Core/Config/Model/TransactionalConfig.php b/src/Core/Config/Model/TransactionalConfig.php new file mode 100644 index 000000000..e9aa71160 --- /dev/null +++ b/src/Core/Config/Model/TransactionalConfig.php @@ -0,0 +1,89 @@ +. + * + */ + +namespace Friendica\Core\Config\Model; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Config\Capability\ISetConfigValuesTransactional; +use Friendica\Core\Config\Exception\ConfigPersistenceException; +use Friendica\Core\Config\ValueObject\Cache; + +/** + * config class, which sets values into a temporary buffer until "save()" is called + */ +class TransactionalConfig implements ISetConfigValuesTransactional +{ + /** @var IManageConfigValues */ + protected $config; + /** @var Cache */ + protected $cache; + /** @var Cache */ + protected $delCache; + + public function __construct(IManageConfigValues $config) + { + $this->config = $config; + $this->cache = new Cache(); + $this->delCache = new Cache(); + } + + /** {@inheritDoc} */ + public function get(string $cat, string $key) + { + return !$this->delCache->get($cat, $key) ? + ($this->cache->get($cat, $key) ?? $this->config->get($cat, $key)) : + null; + } + + /** {@inheritDoc} */ + public function set(string $cat, string $key, $value): ISetConfigValuesTransactional + { + $this->cache->set($cat, $key, $value, Cache::SOURCE_DATA); + + return $this; + } + + + /** {@inheritDoc} */ + public function delete(string $cat, string $key): ISetConfigValuesTransactional + { + $this->cache->delete($cat, $key); + $this->delCache->set($cat, $key, 'deleted'); + + return $this; + } + + /** {@inheritDoc} */ + public function save(): void + { + try { + $newCache = $this->config->getCache()->merge($this->cache); + $newCache = $newCache->diff($this->delCache); + $this->config->load($newCache); + + // flush current cache + $this->cache = new Cache(); + $this->delCache = new Cache(); + } catch (\Exception $e) { + throw new ConfigPersistenceException('Cannot save config', $e); + } + } +} diff --git a/src/Core/Config/ValueObject/Cache.php b/src/Core/Config/ValueObject/Cache.php index a074414bf..305c00d33 100644 --- a/src/Core/Config/ValueObject/Cache.php +++ b/src/Core/Config/ValueObject/Cache.php @@ -279,4 +279,71 @@ class Cache return $return; } + + /** + * Merges a new Cache into the existing one and returns the merged Cache + * + * @param Cache $cache The cache, which should get merged into this Cache + * + * @return Cache The merged Cache + */ + public function merge(Cache $cache): Cache + { + $newConfig = $this->config; + $newSource = $this->source; + + $categories = array_keys($cache->config); + + foreach ($categories as $category) { + if (is_array($cache->config[$category])) { + $keys = array_keys($cache->config[$category]); + + foreach ($keys as $key) { + $newConfig[$category][$key] = $cache->config[$category][$key]; + $newSource[$category][$key] = $cache->source[$category][$key]; + } + } + } + + $newCache = new Cache(); + $newCache->config = $newConfig; + $newCache->source = $newSource; + + return $newCache; + } + + + /** + * Diffs a new Cache into the existing one and returns the diffed Cache + * + * @param Cache $cache The cache, which should get deleted for the current Cache + * + * @return Cache The diffed Cache + */ + public function diff(Cache $cache): Cache + { + $newConfig = $this->config; + $newSource = $this->source; + + $categories = array_keys($cache->config); + + foreach ($categories as $category) { + if (is_array($cache->config[$category])) { + $keys = array_keys($cache->config[$category]); + + foreach ($keys as $key) { + if (!is_null($newConfig[$category][$key] ?? null)) { + unset($newConfig[$category][$key]); + unset($newSource[$category][$key]); + } + } + } + } + + $newCache = new Cache(); + $newCache->config = $newConfig; + $newCache->source = $newSource; + + return $newCache; + } } diff --git a/src/Core/Update.php b/src/Core/Update.php index 9a2ebe1bb..a02645783 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -160,9 +160,10 @@ class Update Logger::warning('Pre update failed', ['version' => $version]); DI::config()->set('system', 'update', Update::FAILED); DI::lock()->release('dbupdate'); - DI::config()->set('system', 'maintenance', false, false); - DI::config()->delete('system', 'maintenance_reason', false); - DI::config()->save(); + DI::config()->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); return $r; } else { Logger::notice('Pre update executed.', ['version' => $version]); @@ -182,9 +183,10 @@ class Update Logger::error('Update ERROR.', ['from' => $stored, 'to' => $current, 'retval' => $retval]); DI::config()->set('system', 'update', Update::FAILED); DI::lock()->release('dbupdate'); - DI::config()->set('system', 'maintenance', false, false); - DI::config()->delete('system', 'maintenance_reason', false); - DI::config()->save(); + DI::config()->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); return $retval; } else { Logger::notice('Database structure update finished.', ['from' => $stored, 'to' => $current]); @@ -200,9 +202,10 @@ class Update Logger::warning('Post update failed', ['version' => $version]); DI::config()->set('system', 'update', Update::FAILED); DI::lock()->release('dbupdate'); - DI::config()->set('system', 'maintenance', false, false); - DI::config()->delete('system', 'maintenance_reason', false); - DI::config()->save(); + DI::config()->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); return $r; } else { DI::config()->set('system', 'build', $version); @@ -213,9 +216,10 @@ class Update DI::config()->set('system', 'build', $current); DI::config()->set('system', 'update', Update::SUCCESS); DI::lock()->release('dbupdate'); - DI::config()->set('system', 'maintenance', false, false); - DI::config()->delete('system', 'maintenance_reason', false); - DI::config()->save(); + DI::config()->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); Logger::notice('Update success.', ['from' => $stored, 'to' => $current]); if ($sendMail) { diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index e3af408b9..ed2a5e30e 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -182,9 +182,10 @@ class DBStructure $status = self::update($verbose, true); if ($enable_maintenance_mode) { - DI::config()->set('system', 'maintenance', false, false); - DI::config()->delete('system', 'maintenance_reason', false); - DI::config()->save(); + DI::config()->transactional() + ->set('system', 'maintenance', false) + ->delete('system', 'maintenance_reason') + ->save(); } return $status; diff --git a/src/Module/Admin/Site.php b/src/Module/Admin/Site.php index f572d3e72..50a7ee868 100644 --- a/src/Module/Admin/Site.php +++ b/src/Module/Admin/Site.php @@ -48,8 +48,6 @@ class Site extends BaseAdmin self::checkFormSecurityTokenRedirectOnError('/admin/site', 'admin_site'); - $a = DI::app(); - if (!empty($_POST['republish_directory'])) { Worker::add(Worker::PRIORITY_LOW, 'Directory'); return; @@ -146,9 +144,11 @@ class Site extends BaseAdmin $relay_user_tags = !empty($_POST['relay_user_tags']); $active_panel = (!empty($_POST['active_panel']) ? "#" . trim($_POST['active_panel']) : ''); + $transactionConfig = DI::config()->transactional(); + // Has the directory url changed? If yes, then resubmit the existing profiles there if ($global_directory != DI::config()->get('system', 'directory') && ($global_directory != '')) { - DI::config()->set('system', 'directory', $global_directory, false); + $transactionConfig->set('system', 'directory', $global_directory); Worker::add(Worker::PRIORITY_LOW, 'Directory'); } @@ -194,133 +194,133 @@ class Site extends BaseAdmin ); } } - DI::config()->set('system', 'ssl_policy' , $ssl_policy, false); - DI::config()->set('system', 'maxloadavg' , $maxloadavg, false); - DI::config()->set('system', 'min_memory' , $min_memory, false); - DI::config()->set('system', 'optimize_tables' , $optimize_tables, false); - DI::config()->set('system', 'contact_discovery' , $contact_discovery, false); - DI::config()->set('system', 'synchronize_directory' , $synchronize_directory, false); - DI::config()->set('system', 'poco_requery_days' , $poco_requery_days, false); - DI::config()->set('system', 'poco_discovery' , $poco_discovery, false); - DI::config()->set('system', 'poco_local_search' , $poco_local_search, false); - DI::config()->set('system', 'nodeinfo' , $nodeinfo, false); - DI::config()->set('config', 'sitename' , $sitename, false); - DI::config()->set('config', 'sender_email' , $sender_email, false); - DI::config()->set('system', 'suppress_tags' , $suppress_tags, false); - DI::config()->set('system', 'shortcut_icon' , $shortcut_icon, false); - DI::config()->set('system', 'touch_icon' , $touch_icon, false); + $transactionConfig->set('system', 'ssl_policy' , $ssl_policy); + $transactionConfig->set('system', 'maxloadavg' , $maxloadavg); + $transactionConfig->set('system', 'min_memory' , $min_memory); + $transactionConfig->set('system', 'optimize_tables' , $optimize_tables); + $transactionConfig->set('system', 'contact_discovery' , $contact_discovery); + $transactionConfig->set('system', 'synchronize_directory' , $synchronize_directory); + $transactionConfig->set('system', 'poco_requery_days' , $poco_requery_days); + $transactionConfig->set('system', 'poco_discovery' , $poco_discovery); + $transactionConfig->set('system', 'poco_local_search' , $poco_local_search); + $transactionConfig->set('system', 'nodeinfo' , $nodeinfo); + $transactionConfig->set('config', 'sitename' , $sitename); + $transactionConfig->set('config', 'sender_email' , $sender_email); + $transactionConfig->set('system', 'suppress_tags' , $suppress_tags); + $transactionConfig->set('system', 'shortcut_icon' , $shortcut_icon); + $transactionConfig->set('system', 'touch_icon' , $touch_icon); if ($banner == "") { - DI::config()->delete('system', 'banner', false); + $transactionConfig->delete('system', 'banner'); } else { - DI::config()->set('system', 'banner', $banner, false); + $transactionConfig->set('system', 'banner', $banner); } if (empty($email_banner)) { - DI::config()->delete('system', 'email_banner', false); + $transactionConfig->delete('system', 'email_banner'); } else { - DI::config()->set('system', 'email_banner', $email_banner, false); + $transactionConfig->set('system', 'email_banner', $email_banner); } if (empty($additional_info)) { - DI::config()->delete('config', 'info', false); + $transactionConfig->delete('config', 'info'); } else { - DI::config()->set('config', 'info', $additional_info, false); + $transactionConfig->set('config', 'info', $additional_info); } - DI::config()->set('system', 'language', $language, false); - DI::config()->set('system', 'theme', $theme, false); + $transactionConfig->set('system', 'language', $language); + $transactionConfig->set('system', 'theme', $theme); Theme::install($theme); if ($theme_mobile == '---') { - DI::config()->delete('system', 'mobile-theme', false); + $transactionConfig->delete('system', 'mobile-theme'); } else { - DI::config()->set('system', 'mobile-theme', $theme_mobile, false); + $transactionConfig->set('system', 'mobile-theme', $theme_mobile); } if ($singleuser == '---') { - DI::config()->delete('system', 'singleuser', false); + $transactionConfig->delete('system', 'singleuser'); } else { - DI::config()->set('system', 'singleuser', $singleuser, false); + $transactionConfig->set('system', 'singleuser', $singleuser); } if (preg_match('/\d+(?:\s*[kmg])?/i', $maximagesize)) { - DI::config()->set('system', 'maximagesize', $maximagesize, false); + $transactionConfig->set('system', 'maximagesize', $maximagesize); } else { DI::sysmsg()->addNotice(DI::l10n()->t('%s is no valid input for maximum image size', $maximagesize)); } - DI::config()->set('system', 'max_image_length' , $maximagelength, false); - DI::config()->set('system', 'jpeg_quality' , $jpegimagequality, false); + $transactionConfig->set('system', 'max_image_length' , $maximagelength); + $transactionConfig->set('system', 'jpeg_quality' , $jpegimagequality); - DI::config()->set('config', 'register_policy' , $register_policy, false); - DI::config()->set('system', 'max_daily_registrations', $daily_registrations, false); - DI::config()->set('system', 'account_abandon_days' , $abandon_days, false); - DI::config()->set('config', 'register_text' , $register_text, false); - DI::config()->set('system', 'allowed_sites' , $allowed_sites, false); - DI::config()->set('system', 'allowed_email' , $allowed_email, false); - DI::config()->set('system', 'forbidden_nicknames' , $forbidden_nicknames, false); - DI::config()->set('system', 'system_actor_name' , $system_actor_name, false); - DI::config()->set('system', 'no_oembed_rich_content' , $no_oembed_rich_content, false); - DI::config()->set('system', 'allowed_oembed' , $allowed_oembed, false); - DI::config()->set('system', 'block_public' , $block_public, false); - DI::config()->set('system', 'publish_all' , $force_publish, false); - DI::config()->set('system', 'newuser_private' , $newuser_private, false); - DI::config()->set('system', 'enotify_no_content' , $enotify_no_content, false); - DI::config()->set('system', 'disable_embedded' , $disable_embedded, false); - DI::config()->set('system', 'allow_users_remote_self', $allow_users_remote_self, false); - DI::config()->set('system', 'explicit_content' , $explicit_content, false); - DI::config()->set('system', 'proxify_content' , $proxify_content, false); - DI::config()->set('system', 'cache_contact_avatar' , $cache_contact_avatar, false); - DI::config()->set('system', 'check_new_version_url' , $check_new_version_url, false); + $transactionConfig->set('config', 'register_policy' , $register_policy); + $transactionConfig->set('system', 'max_daily_registrations', $daily_registrations); + $transactionConfig->set('system', 'account_abandon_days' , $abandon_days); + $transactionConfig->set('config', 'register_text' , $register_text); + $transactionConfig->set('system', 'allowed_sites' , $allowed_sites); + $transactionConfig->set('system', 'allowed_email' , $allowed_email); + $transactionConfig->set('system', 'forbidden_nicknames' , $forbidden_nicknames); + $transactionConfig->set('system', 'system_actor_name' , $system_actor_name); + $transactionConfig->set('system', 'no_oembed_rich_content' , $no_oembed_rich_content); + $transactionConfig->set('system', 'allowed_oembed' , $allowed_oembed); + $transactionConfig->set('system', 'block_public' , $block_public); + $transactionConfig->set('system', 'publish_all' , $force_publish); + $transactionConfig->set('system', 'newuser_private' , $newuser_private); + $transactionConfig->set('system', 'enotify_no_content' , $enotify_no_content); + $transactionConfig->set('system', 'disable_embedded' , $disable_embedded); + $transactionConfig->set('system', 'allow_users_remote_self', $allow_users_remote_self); + $transactionConfig->set('system', 'explicit_content' , $explicit_content); + $transactionConfig->set('system', 'proxify_content' , $proxify_content); + $transactionConfig->set('system', 'cache_contact_avatar' , $cache_contact_avatar); + $transactionConfig->set('system', 'check_new_version_url' , $check_new_version_url); - DI::config()->set('system', 'block_extended_register', !$enable_multi_reg, false); - DI::config()->set('system', 'no_openid' , !$enable_openid, false); - DI::config()->set('system', 'no_regfullname' , !$enable_regfullname, false); - DI::config()->set('system', 'register_notification' , $register_notification, false); - DI::config()->set('system', 'community_page_style' , $community_page_style, false); - DI::config()->set('system', 'max_author_posts_community_page', $max_author_posts_community_page, false); - DI::config()->set('system', 'verifyssl' , $verifyssl, false); - DI::config()->set('system', 'proxyuser' , $proxyuser, false); - DI::config()->set('system', 'proxy' , $proxy, false); - DI::config()->set('system', 'curl_timeout' , $timeout, false); - DI::config()->set('system', 'imap_disabled' , !$mail_enabled && function_exists('imap_open'), false); - DI::config()->set('system', 'ostatus_disabled' , !$ostatus_enabled, false); - DI::config()->set('system', 'diaspora_enabled' , $diaspora_enabled, false); + $transactionConfig->set('system', 'block_extended_register', !$enable_multi_reg); + $transactionConfig->set('system', 'no_openid' , !$enable_openid); + $transactionConfig->set('system', 'no_regfullname' , !$enable_regfullname); + $transactionConfig->set('system', 'register_notification' , $register_notification); + $transactionConfig->set('system', 'community_page_style' , $community_page_style); + $transactionConfig->set('system', 'max_author_posts_community_page', $max_author_posts_community_page); + $transactionConfig->set('system', 'verifyssl' , $verifyssl); + $transactionConfig->set('system', 'proxyuser' , $proxyuser); + $transactionConfig->set('system', 'proxy' , $proxy); + $transactionConfig->set('system', 'curl_timeout' , $timeout); + $transactionConfig->set('system', 'imap_disabled' , !$mail_enabled && function_exists('imap_open')); + $transactionConfig->set('system', 'ostatus_disabled' , !$ostatus_enabled); + $transactionConfig->set('system', 'diaspora_enabled' , $diaspora_enabled); - DI::config()->set('config', 'private_addons' , $private_addons, false); + $transactionConfig->set('config', 'private_addons' , $private_addons); - DI::config()->set('system', 'force_ssl' , $force_ssl, false); - DI::config()->set('system', 'hide_help' , !$show_help, false); + $transactionConfig->set('system', 'force_ssl' , $force_ssl); + $transactionConfig->set('system', 'hide_help' , !$show_help); - DI::config()->set('system', 'dbclean' , $dbclean, false); - DI::config()->set('system', 'dbclean-expire-days' , $dbclean_expire_days, false); - DI::config()->set('system', 'dbclean_expire_conversation', $dbclean_expire_conv, false); + $transactionConfig->set('system', 'dbclean' , $dbclean); + $transactionConfig->set('system', 'dbclean-expire-days' , $dbclean_expire_days); + $transactionConfig->set('system', 'dbclean_expire_conversation', $dbclean_expire_conv); if ($dbclean_unclaimed == 0) { $dbclean_unclaimed = $dbclean_expire_days; } - DI::config()->set('system', 'dbclean-expire-unclaimed', $dbclean_unclaimed, false); + $transactionConfig->set('system', 'dbclean-expire-unclaimed', $dbclean_unclaimed); - DI::config()->set('system', 'max_comments', $max_comments, false); - DI::config()->set('system', 'max_display_comments', $max_display_comments, false); + $transactionConfig->set('system', 'max_comments', $max_comments); + $transactionConfig->set('system', 'max_display_comments', $max_display_comments); if ($temppath != '') { $temppath = BasePath::getRealPath($temppath); } - DI::config()->set('system', 'temppath', $temppath, false); + $transactionConfig->set('system', 'temppath', $temppath); - DI::config()->set('system', 'only_tag_search' , $only_tag_search, false); - DI::config()->set('system', 'compute_group_counts', $compute_group_counts, false); + $transactionConfig->set('system', 'only_tag_search' , $only_tag_search); + $transactionConfig->set('system', 'compute_group_counts', $compute_group_counts); - DI::config()->set('system', 'worker_queues' , $worker_queues, false); - DI::config()->set('system', 'worker_fastlane' , $worker_fastlane, false); + $transactionConfig->set('system', 'worker_queues' , $worker_queues); + $transactionConfig->set('system', 'worker_fastlane' , $worker_fastlane); - DI::config()->set('system', 'relay_directly' , $relay_directly, false); - DI::config()->set('system', 'relay_scope' , $relay_scope, false); - DI::config()->set('system', 'relay_server_tags', $relay_server_tags, false); - DI::config()->set('system', 'relay_deny_tags' , $relay_deny_tags, false); - DI::config()->set('system', 'relay_user_tags' , $relay_user_tags, false); + $transactionConfig->set('system', 'relay_directly' , $relay_directly); + $transactionConfig->set('system', 'relay_scope' , $relay_scope); + $transactionConfig->set('system', 'relay_server_tags', $relay_server_tags); + $transactionConfig->set('system', 'relay_deny_tags' , $relay_deny_tags); + $transactionConfig->set('system', 'relay_user_tags' , $relay_user_tags); - DI::config()->save(); + $transactionConfig->save(); DI::baseUrl()->redirect('admin/site' . $active_panel); } @@ -334,8 +334,8 @@ class Site extends BaseAdmin if (DI::config()->get('system', 'directory_submit_url') && !DI::config()->get('system', 'directory')) { - DI::config()->set('system', 'directory', dirname(DI::config()->get('system', 'directory_submit_url')), false); - DI::config()->delete('system', 'directory_submit_url', false); + DI::config()->set('system', 'directory', dirname(DI::config()->get('system', 'directory_submit_url'))); + DI::config()->delete('system', 'directory_submit_url'); } /* Installed themes */ diff --git a/tests/src/Core/Config/Cache/CacheTest.php b/tests/src/Core/Config/Cache/CacheTest.php index 8b2c24da3..9d72774c4 100644 --- a/tests/src/Core/Config/Cache/CacheTest.php +++ b/tests/src/Core/Config/Cache/CacheTest.php @@ -358,4 +358,52 @@ class CacheTest extends MockedTest $this->assertEquals(['system' => ['test_2' => 'with_data']], $configCache->getDataBySource(Cache::SOURCE_DATA)); $this->assertEquals($data, $configCache->getDataBySource(Cache::SOURCE_FILE)); } + + /** + * @dataProvider dataTests + */ + public function testMerge($data) + { + $configCache = new Cache(); + $configCache->load($data, Cache::SOURCE_FILE); + + $configCache->set('system', 'test_2','with_data', Cache::SOURCE_DATA); + $configCache->set('config', 'test_override','with_another_data', Cache::SOURCE_DATA); + + $newCache = new Cache(); + $newCache->set('config', 'test_override','override it again', Cache::SOURCE_DATA); + $newCache->set('system', 'test_3','new value', Cache::SOURCE_DATA); + + $mergedCache = $configCache->merge($newCache); + + self::assertEquals('with_data', $mergedCache->get('system', 'test_2')); + self::assertEquals('override it again', $mergedCache->get('config', 'test_override')); + self::assertEquals('new value', $mergedCache->get('system', 'test_3')); + } + + /** + * @dataProvider dataTests + */ + public function testDiff($data) + { + $configCache = new Cache(); + $configCache->load($data, Cache::SOURCE_FILE); + + $configCache->set('system', 'test_2','with_data', Cache::SOURCE_DATA); + $configCache->set('config', 'test_override','with_another_data', Cache::SOURCE_DATA); + + $newCache = new Cache(); + $newCache->set('config', 'test_override','override it again', Cache::SOURCE_DATA); + $newCache->set('system', 'test_3','new value', Cache::SOURCE_DATA); + + $mergedCache = $configCache->diff($newCache); + + print_r($mergedCache); + + self::assertEquals('with_data', $mergedCache->get('system', 'test_2')); + // existing entry was dropped + self::assertNull($mergedCache->get('config', 'test_override')); + // the newCache entry wasn't set, because we Diff + self::assertNull($mergedCache->get('system', 'test_3')); + } } diff --git a/tests/src/Core/Config/Cache/ConfigFileLoaderTest.php b/tests/src/Core/Config/Cache/ConfigFileLoaderTest.php index e8443611f..aed55f429 100644 --- a/tests/src/Core/Config/Cache/ConfigFileLoaderTest.php +++ b/tests/src/Core/Config/Cache/ConfigFileLoaderTest.php @@ -21,8 +21,8 @@ namespace Friendica\Test\src\Core\Config\Cache; -use Friendica\Core\Config\Cache; use Friendica\Core\Config\Factory\Config; +use Friendica\Core\Config\ValueObject\Cache; use Friendica\Test\MockedTest; use Friendica\Test\Util\VFSTrait; use Friendica\Core\Config\Util\ConfigFileManager; @@ -51,7 +51,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -77,7 +77,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); } @@ -106,7 +106,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -143,7 +143,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -179,7 +179,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -270,7 +270,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -304,7 +304,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -338,7 +338,7 @@ class ConfigFileLoaderTest extends MockedTest $this->root->url() . DIRECTORY_SEPARATOR . Config::CONFIG_DIR, $this->root->url() . DIRECTORY_SEPARATOR . Config::STATIC_DIR ); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -354,7 +354,7 @@ class ConfigFileLoaderTest extends MockedTest $this->delConfigFile('local.config.php'); $configFileLoader = (new Config())->createConfigFileLoader($this->root->url(), ['FRIENDICA_CONFIG_DIR' => '/a/wrong/dir/']); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -380,7 +380,7 @@ class ConfigFileLoaderTest extends MockedTest ->setContent(file_get_contents($fileDir . 'B.config.php')); $configFileLoader = (new Config())->createConfigFileLoader($this->root->url(), ['FRIENDICA_CONFIG_DIR' => $this->root->getChild('config2')->url()]); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); @@ -403,18 +403,18 @@ class ConfigFileLoaderTest extends MockedTest ->setContent(file_get_contents($fileDir . 'B.config.php')); $configFileLoader = (new Config())->createConfigFileLoader($this->root->url(), ['FRIENDICA_CONFIG_DIR' => $this->root->getChild('config2')->url()]); - $configCache = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache = new Cache(); $configFileLoader->setupCache($configCache); // overwrite some data and save it back to the config file - $configCache->set('system', 'test', 'it', \Friendica\Core\Config\ValueObject\Cache::SOURCE_DATA); - $configCache->set('config', 'test', 'it', \Friendica\Core\Config\ValueObject\Cache::SOURCE_DATA); - $configCache->set('system', 'test_2', 2, \Friendica\Core\Config\ValueObject\Cache::SOURCE_DATA); + $configCache->set('system', 'test', 'it', Cache::SOURCE_DATA); + $configCache->set('config', 'test', 'it', Cache::SOURCE_DATA); + $configCache->set('system', 'test_2', 2, Cache::SOURCE_DATA); $configFileLoader->saveData($configCache); // Reload the configCache with the new values - $configCache2 = new \Friendica\Core\Config\ValueObject\Cache(); + $configCache2 = new Cache(); $configFileLoader->setupCache($configCache2); self::assertEquals($configCache, $configCache2); @@ -425,6 +425,6 @@ class ConfigFileLoaderTest extends MockedTest ], 'config' => [ 'test' => 'it' - ]], $configCache2->getDataBySource(\Friendica\Core\Config\ValueObject\Cache::SOURCE_DATA)); + ]], $configCache2->getDataBySource(Cache::SOURCE_DATA)); } } diff --git a/tests/src/Core/Config/TransactionalConfigTest.php b/tests/src/Core/Config/TransactionalConfigTest.php new file mode 100644 index 000000000..b42fee97c --- /dev/null +++ b/tests/src/Core/Config/TransactionalConfigTest.php @@ -0,0 +1,110 @@ +setUpVfsDir(); + + $this->configFileManager = new ConfigFileManager($this->root->url(), $this->root->url() . '/config/', $this->root->url() . '/static/'); + } + + public function dataTests(): array + { + return [ + 'default' => [ + 'data' => include dirname(__FILE__, 4) . '/datasets/B.node.config.php', + ] + ]; + } + + public function testInstance() + { + $config = new Config($this->configFileManager, new Cache()); + $transactionalConfig = new TransactionalConfig($config); + + self::assertInstanceOf(ISetConfigValuesTransactional::class, $transactionalConfig); + self::assertInstanceOf(TransactionalConfig::class, $transactionalConfig); + } + + public function testTransactionalConfig() + { + $config = new Config($this->configFileManager, new Cache()); + $config->set('config', 'key1', 'value1'); + $config->set('system', 'key2', 'value2'); + $config->set('system', 'keyDel', 'valueDel'); + $config->set('delete', 'keyDel', 'catDel'); + + $transactionalConfig = new TransactionalConfig($config); + self::assertEquals('value1', $transactionalConfig->get('config', 'key1')); + self::assertEquals('value2', $transactionalConfig->get('system', 'key2')); + self::assertEquals('valueDel', $transactionalConfig->get('system', 'keyDel')); + self::assertEquals('catDel', $transactionalConfig->get('delete', 'keyDel')); + // the config file knows it as well immediately + $tempData = include $this->root->url() . '/config/' . ConfigFileManager::CONFIG_DATA_FILE; + self::assertEquals('value1', $tempData['config']['key1'] ?? null); + self::assertEquals('value2', $tempData['system']['key2'] ?? null); + + // new key-value + $transactionalConfig->set('transaction', 'key3', 'value3'); + // overwrite key-value + $transactionalConfig->set('config', 'key1', 'changedValue1'); + // delete key-value + $transactionalConfig->delete('system', 'keyDel'); + // delete last key of category - so the category is gone + $transactionalConfig->delete('delete', 'keyDel'); + + // The main config still doesn't know about the change + self::assertNull($config->get('transaction', 'key3')); + self::assertEquals('value1', $config->get('config', 'key1')); + self::assertEquals('valueDel', $config->get('system', 'keyDel')); + self::assertEquals('catDel', $config->get('delete', 'keyDel')); + // but the transaction config of course knows it + self::assertEquals('value3', $transactionalConfig->get('transaction', 'key3')); + self::assertEquals('changedValue1', $transactionalConfig->get('config', 'key1')); + self::assertNull($transactionalConfig->get('system', 'keyDel')); + self::assertNull($transactionalConfig->get('delete', 'keyDel')); + // The config file still doesn't know it either + $tempData = include $this->root->url() . '/config/' . ConfigFileManager::CONFIG_DATA_FILE; + self::assertEquals('value1', $tempData['config']['key1'] ?? null); + self::assertEquals('value2', $tempData['system']['key2'] ?? null); + self::assertEquals('catDel', $tempData['delete']['keyDel'] ?? null); + self::assertNull($tempData['transaction']['key3'] ?? null); + + // save it back! + $transactionalConfig->save(); + + // Now every config and file knows the change + self::assertEquals('changedValue1', $config->get('config', 'key1')); + self::assertEquals('value3', $config->get('transaction', 'key3')); + self::assertNull($config->get('system', 'keyDel')); + self::assertNull($config->get('delete', 'keyDel')); + self::assertEquals('value3', $transactionalConfig->get('transaction', 'key3')); + self::assertEquals('changedValue1', $transactionalConfig->get('config', 'key1')); + self::assertNull($transactionalConfig->get('system', 'keyDel')); + $tempData = include $this->root->url() . '/config/' . ConfigFileManager::CONFIG_DATA_FILE; + self::assertEquals('changedValue1', $tempData['config']['key1'] ?? null); + self::assertEquals('value2', $tempData['system']['key2'] ?? null); + self::assertEquals('value3', $tempData['transaction']['key3'] ?? null); + self::assertNull($tempData['system']['keyDel'] ?? null); + self::assertNull($tempData['delete']['keyDel'] ?? null); + // the whole category should be gone + self::assertNull($tempData['delete'] ?? null); + } +} diff --git a/update.php b/update.php index ad33dde01..7ad6e432f 100644 --- a/update.php +++ b/update.php @@ -1184,11 +1184,13 @@ function update_1508() { $config = DBA::selectToArray('config'); + $newConfig = DI::config()->transactional(); + foreach ($config as $entry) { - DI::config()->set($entry['cat'], $entry['k'], $entry['v'], false); + $newConfig->set($entry['cat'], $entry['k'], $entry['v']); } - DI::config()->save(); + $newConfig->save(); DBA::e("DELETE FROM `config`"); }