diff --git a/database.sql b/database.sql index 843db1f6c..e890d7421 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2023.03-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1513 +-- DB_UPDATE_VERSION 1514 -- ------------------------------------------ @@ -495,6 +495,18 @@ CREATE TABLE IF NOT EXISTS `cache` ( INDEX `k_expires` (`k`,`expires`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Stores temporary data'; +-- +-- TABLE config +-- +CREATE TABLE IF NOT EXISTS `config` ( + `id` int unsigned NOT NULL auto_increment COMMENT '', + `cat` varbinary(50) NOT NULL DEFAULT '' COMMENT 'The category of the entry', + `k` varbinary(50) NOT NULL DEFAULT '' COMMENT 'The key of the entry', + `v` mediumtext COMMENT '', + PRIMARY KEY(`id`), + UNIQUE INDEX `cat_k` (`cat`,`k`) +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='main configuration storage'; + -- -- TABLE contact-relation -- diff --git a/doc/database.md b/doc/database.md index 95e0367af..edfb7b822 100644 --- a/doc/database.md +++ b/doc/database.md @@ -18,6 +18,7 @@ Database Tables | [arrived-activity](help/database/db_arrived-activity) | Id of arrived activities | | [attach](help/database/db_attach) | file attachments | | [cache](help/database/db_cache) | Stores temporary data | +| [config](help/database/db_config) | main configuration storage | | [contact](help/database/db_contact) | contact table | | [contact-relation](help/database/db_contact-relation) | Contact relations | | [conv](help/database/db_conv) | private messages | diff --git a/mods/local.config.ci.php b/mods/local.config.ci.php new file mode 100644 index 000000000..66e80a809 --- /dev/null +++ b/mods/local.config.ci.php @@ -0,0 +1,53 @@ +. + * + */ + +return [ + 'database' => [ + 'hostname' => 'localhost', + 'username' => 'friendica', + 'password' => 'friendica', + 'database' => 'friendica', + 'charset' => 'utf8mb4', + ], + + // **************************************************************** + // The configuration below will be overruled by the admin panel. + // Changes made below will only have an effect if the database does + // not contain any configuration for the friendica system. + // **************************************************************** + + 'config' => [ + 'hostname' => 'friendica.local', + 'admin_email' => 'admin@friendica.local', + 'sitename' => 'Friendica Social Network', + 'register_policy' => \Friendica\Module\Register::OPEN, + 'register_text' => '', + ], + 'system' => [ + 'default_timezone' => 'UTC', + 'language' => 'en', + 'ssl_policy' => \Friendica\App\BaseURL::SSL_POLICY_SELFSIGN, + 'url' => 'https://friendica.local', + 'urlpath' => '', + // don't start unexpected worker.php processes during test! + 'worker_dont_fork' => true, + ], +]; diff --git a/src/Core/Config/Capability/IManageConfigValues.php b/src/Core/Config/Capability/IManageConfigValues.php index 2c3d6da3c..4f785d007 100644 --- a/src/Core/Config/Capability/IManageConfigValues.php +++ b/src/Core/Config/Capability/IManageConfigValues.php @@ -31,7 +31,7 @@ use Friendica\Core\Config\ValueObject\Cache; interface IManageConfigValues { /** - * Reloads all configuration values (from filesystem and environment variables) + * Reloads all configuration values from the persistence layer * * All configuration values of the system are stored in the cache. * diff --git a/src/Core/Config/Model/Config.php b/src/Core/Config/Model/Config.php deleted file mode 100644 index 46d5643b3..000000000 --- a/src/Core/Config/Model/Config.php +++ /dev/null @@ -1,139 +0,0 @@ -. - * - */ - -namespace Friendica\Core\Config\Model; - -use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Config\Capability\ISetConfigValuesTransactionally; -use Friendica\Core\Config\Exception\ConfigFileException; -use Friendica\Core\Config\Exception\ConfigPersistenceException; -use Friendica\Core\Config\Util\ConfigFileManager; -use Friendica\Core\Config\ValueObject\Cache; - -/** - * Configuration model, which manages the whole system configuration - */ -class Config implements IManageConfigValues -{ - /** @var Cache */ - protected $configCache; - - /** @var ConfigFileManager */ - protected $configFileManager; - - /** - * @param ConfigFileManager $configFileManager The configuration file manager to save back configs - * @param Cache $configCache The configuration cache (based on the config-files) - */ - public function __construct(ConfigFileManager $configFileManager, Cache $configCache) - { - $this->configFileManager = $configFileManager; - $this->configCache = $configCache; - } - - /** - * 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 setCacheAndSave(Cache $cache) - { - $this->configCache = $cache; - $this->save(); - } - - /** - * {@inheritDoc} - */ - public function getCache(): Cache - { - return $this->configCache; - } - - /** {@inheritDoc} */ - public function beginTransaction(): ISetConfigValuesTransactionally - { - return new ConfigTransaction($this); - } - - /** - * Saves the current Configuration back into the data config. - * @see ConfigFileManager::CONFIG_DATA_FILE - */ - protected function save() - { - try { - $this->configFileManager->saveData($this->configCache); - // reload after the save to possible reload default values of lower source-priorities again - $this->reload(); - } catch (ConfigFileException $e) { - throw new ConfigPersistenceException('Cannot save config', $e); - } - } - - /** {@inheritDoc} */ - public function reload() - { - $configCache = new Cache(); - - try { - $this->configFileManager->setupCache($configCache); - } catch (ConfigFileException $e) { - throw new ConfigPersistenceException('Cannot reload config', $e); - } - $this->configCache = $configCache; - } - - /** {@inheritDoc} */ - public function get(string $cat, string $key = null, $default_value = null) - { - return $this->configCache->get($cat, $key) ?? $default_value; - } - - /** {@inheritDoc} */ - public function set(string $cat, string $key, $value): bool - { - if ($this->configCache->set($cat, $key, $value, Cache::SOURCE_DATA)) { - $this->save(); - return true; - } else { - return false; - } - } - - /** {@inheritDoc} */ - public function delete(string $cat, string $key): bool - { - if ($this->configCache->delete($cat, $key)) { - $this->save(); - return true; - } else { - return false; - } - } -} diff --git a/src/Core/Config/Model/ConfigTransaction.php b/src/Core/Config/Model/ConfigTransaction.php index b9310301f..da5f5f6f3 100644 --- a/src/Core/Config/Model/ConfigTransaction.php +++ b/src/Core/Config/Model/ConfigTransaction.php @@ -34,20 +34,23 @@ class ConfigTransaction implements ISetConfigValuesTransactionally /** @var IManageConfigValues */ protected $config; /** @var Cache */ - protected $cache; + protected $setCache; + /** @var Cache */ + protected $delCache; /** @var bool field to check if something is to save */ protected $changedConfig = false; - public function __construct(IManageConfigValues $config) + public function __construct(DatabaseConfig $config) { - $this->config = $config; - $this->cache = clone $config->getCache(); + $this->config = $config; + $this->setCache = new Cache(); + $this->delCache = new Cache(); } /** {@inheritDoc} */ public function set(string $cat, string $key, $value): ISetConfigValuesTransactionally { - $this->cache->set($cat, $key, $value, Cache::SOURCE_DATA); + $this->setCache->set($cat, $key, $value, Cache::SOURCE_DATA); $this->changedConfig = true; return $this; @@ -57,7 +60,7 @@ class ConfigTransaction implements ISetConfigValuesTransactionally /** {@inheritDoc} */ public function delete(string $cat, string $key): ISetConfigValuesTransactionally { - $this->cache->delete($cat, $key); + $this->delCache->set($cat, $key, true, Cache::SOURCE_DATA); $this->changedConfig = true; return $this; @@ -72,8 +75,9 @@ class ConfigTransaction implements ISetConfigValuesTransactionally } try { - $this->config->setCacheAndSave($this->cache); - $this->cache = clone $this->config->getCache(); + $this->config->setAndSave($this->setCache, $this->delCache); + $this->setCache = new Cache(); + $this->delCache = new Cache(); } catch (\Exception $e) { throw new ConfigPersistenceException('Cannot save config', $e); } diff --git a/src/Core/Config/Model/DatabaseConfig.php b/src/Core/Config/Model/DatabaseConfig.php new file mode 100644 index 000000000..01366cf71 --- /dev/null +++ b/src/Core/Config/Model/DatabaseConfig.php @@ -0,0 +1,110 @@ +. + * + */ + +namespace Friendica\Core\Config\Model; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Config\Capability\ISetConfigValuesTransactionally; +use Friendica\Core\Config\Util\SerializeUtil; +use Friendica\Core\Config\ValueObject\Cache; +use Friendica\Database\Database; + +/** + * Complete system configuration model, bound with the database + */ +class DatabaseConfig implements IManageConfigValues +{ + /** @var Database */ + protected $database; + /** @var Cache */ + protected $cache; + + public function __construct(Database $database, Cache $cache) + { + $this->database = $database; + $this->cache = $cache; + + $this->reload(); + } + + /** {@inheritDoc} */ + public function reload() + { + $config = $this->database->selectToArray('config'); + + foreach ($config as $entry) { + $this->cache->set($entry['cat'], $entry['k'], SerializeUtil::maybeUnserialize($entry['v']), Cache::SOURCE_DATA); + } + } + + public function setAndSave(Cache $setCache, Cache $delCache): bool + { + $this->database->transaction(); + + foreach ($setCache->getAll() as $category => $data) { + foreach ($data as $key => $value) { + $this->cache->set($category, $key, $value, Cache::SOURCE_DATA); + $this->database->insert('config', ['cat' => $category, 'k' => $key, 'v' => serialize($value)], Database::INSERT_UPDATE); + } + } + + foreach ($delCache->getAll() as $category => $keys) { + foreach ($keys as $key => $value) { + $this->cache->delete($category, $key); + $this->database->delete('config', ['cat' => $category, 'k' => $key]); + } + } + + return $this->database->commit(); + } + + /** {@inheritDoc} */ + public function get(string $cat, string $key = null, $default_value = null) + { + return $this->cache->get($cat, $key) ?? $default_value; + } + + /** {@inheritDoc} */ + public function set(string $cat, string $key, $value): bool + { + $this->cache->set($cat, $key, $value, Cache::SOURCE_DATA); + return $this->database->insert('config', ['cat' => $cat, 'k' => $key, 'v' => serialize($value)], Database::INSERT_UPDATE); + } + + /** {@inheritDoc} */ + public function beginTransaction(): ISetConfigValuesTransactionally + { + return new ConfigTransaction($this); + } + + /** {@inheritDoc} */ + public function delete(string $cat, string $key): bool + { + $this->cache->delete($cat, $key); + return $this->database->delete('config', ['cat' => $cat, 'k' => $key]); + } + + /** {@inheritDoc} */ + public function getCache(): Cache + { + return $this->cache; + } +} diff --git a/src/Core/Config/Model/ReadOnlyFileConfig.php b/src/Core/Config/Model/ReadOnlyFileConfig.php new file mode 100644 index 000000000..4b32720f0 --- /dev/null +++ b/src/Core/Config/Model/ReadOnlyFileConfig.php @@ -0,0 +1,82 @@ +. + * + */ + +namespace Friendica\Core\Config\Model; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Config\Capability\ISetConfigValuesTransactionally; +use Friendica\Core\Config\Exception\ConfigPersistenceException; +use Friendica\Core\Config\ValueObject\Cache; + +/** + * Creates a basic, readonly model for the file-based configuration + */ +class ReadOnlyFileConfig implements IManageConfigValues +{ + /** @var Cache */ + protected $configCache; + + /** + * @param Cache $configCache The configuration cache (based on the config-files) + */ + public function __construct(Cache $configCache) + { + $this->configCache = $configCache; + } + + /** + * {@inheritDoc} + */ + public function getCache(): Cache + { + return $this->configCache; + } + + /** {@inheritDoc} */ + public function beginTransaction(): ISetConfigValuesTransactionally + { + throw new ConfigPersistenceException('beginTransaction not allowed.'); + } + + /** {@inheritDoc} */ + public function reload() + { + throw new ConfigPersistenceException('reload not allowed.'); + } + + /** {@inheritDoc} */ + public function get(string $cat, string $key = null, $default_value = null) + { + return $this->configCache->get($cat, $key) ?? $default_value; + } + + /** {@inheritDoc} */ + public function set(string $cat, string $key, $value): bool + { + throw new ConfigPersistenceException('set not allowed.'); + } + + /** {@inheritDoc} */ + public function delete(string $cat, string $key): bool + { + throw new ConfigPersistenceException('Save not allowed'); + } +} diff --git a/src/Core/Config/Util/ConfigFileManager.php b/src/Core/Config/Util/ConfigFileManager.php index 011dbf0e4..5241474ec 100644 --- a/src/Core/Config/Util/ConfigFileManager.php +++ b/src/Core/Config/Util/ConfigFileManager.php @@ -111,9 +111,6 @@ class ConfigFileManager // Now load every other config you find inside the 'config/' directory $this->loadCoreConfig($configCache); - // Now load the node.config.php file with the node specific config values (based on admin gui/console actions) - $this->loadDataConfig($configCache); - $configCache->load($this->loadEnvConfig(), Cache::SOURCE_ENV); // In case of install mode, add the found basepath (because there isn't a basepath set yet @@ -166,167 +163,6 @@ class ConfigFileManager } } - /** - * Tries to load the data config file with the overridden data - * - * @param Cache $configCache The Config cache - * - * @throws ConfigFileException In case the config file isn't loadable - */ - private function loadDataConfig(Cache $configCache) - { - $filename = $this->configDir . '/' . self::CONFIG_DATA_FILE; - - if (file_exists($filename) && (filesize($filename) > 0)) { - - // The fallback empty return content - $content = '' as prefix. - */ - $dataArray = eval('?>' . $content); - - if (is_array($dataArray)) { - $configCache->load($dataArray, Cache::SOURCE_DATA); - } - } - } - - /** - * Checks, if the node.config.php is writable - * - * @return bool - */ - public function dataIsWritable(): bool - { - $filename = $this->configDir . '/' . self::CONFIG_DATA_FILE; - - if (file_exists($filename)) { - return is_writable($filename); - } else { - return is_writable($this->configDir); - } - } - - /** - * Saves overridden config entries back into the data.config.php - * - * @param Cache $configCache The config cache - * - * @throws ConfigFileException In case the config file isn't writeable or the data is invalid - */ - public function saveData(Cache $configCache) - { - $filename = $this->configDir . '/' . self::CONFIG_DATA_FILE; - - if (file_exists($filename)) { - $fileExists = true; - } else { - $fileExists = false; - } - - /** - * Creates a read-write stream - * - * @see https://www.php.net/manual/en/function.fopen.php - * @note Open the file for reading and writing. If the file does not exist, it is created. - * If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails - * (as is the case with 'x'). The file pointer is positioned on the beginning of the file. - * - */ - if (($configStream = @fopen($filename, 'c+')) === false) { - throw new ConfigFileException(sprintf('Cannot open file "%s" in mode c+', $filename)); - } - - try { - // We do want an exclusive lock, so we wait until every LOCK_SH (config reading) is unlocked - if (flock($configStream, LOCK_EX)) { - - /** - * If the file exists, we read the whole file again to avoid a race condition with concurrent threads that could have modified the file between the first config read of this thread and now - * Since we're currently exclusive locked, no other process can now change the config again - */ - if ($fileExists) { - // When reading the config file too fast, we get a wrong filesize, "clearstatcache" prevents that - clearstatcache(true, $filename); - $content = fread($configStream, filesize($filename)); - if (!$content) { - throw new ConfigFileException(sprintf('Cannot read file %s', $filename)); - } - - // Event truncating the whole content wouldn't automatically rewind the stream, - // so we need to do it manually - rewind($configStream); - - $dataArray = eval('?>' . $content); - - // Merge the new content into the existing file based config cache and use it - // as the new config cache - if (is_array($dataArray)) { - $fileConfigCache = new Cache(); - $fileConfigCache->load($dataArray, Cache::SOURCE_DATA); - $configCache = $fileConfigCache->merge($configCache); - } - } - - // Only SOURCE_DATA is wanted, the rest isn't part of the node.config.php file - $data = $configCache->getDataBySource(Cache::SOURCE_DATA); - - $encodedData = ConfigFileTransformer::encode($data); - if (!$encodedData) { - throw new ConfigFileException('config source cannot get encoded'); - } - - // Once again to avoid wrong, implicit "filesize" calls during the fwrite() or ftruncate() call - clearstatcache(true, $filename); - if (!ftruncate($configStream, 0) || - !fwrite($configStream, $encodedData) || - !fflush($configStream)) { - throw new ConfigFileException(sprintf('Cannot modify locked file %s', $filename)); - } - } - } finally { - // unlock and close the stream for every circumstances - flock($configStream, LOCK_UN); - fclose($configStream); - } - } - /** * Tries to load the specified addon-configuration and returns the config array. * diff --git a/src/Core/Config/Util/ConfigFileTransformer.php b/src/Core/Config/Util/ConfigFileTransformer.php deleted file mode 100644 index f15972164..000000000 --- a/src/Core/Config/Util/ConfigFileTransformer.php +++ /dev/null @@ -1,237 +0,0 @@ -. - * - */ - -namespace Friendica\Core\Config\Util; - -/** - * Util to transform back the config array into a string - */ -class ConfigFileTransformer -{ - /** - * This method takes an array of config values and applies some standard rules for formatting on it - * - * Beware that the applied rules follow some basic formatting principles for node.config.php - * and doesn't support any custom formatting rules. - * - * f.e. associative array and list formatting are very complex with newlines and indentations, thus there are - * three hardcoded types of formatting for them. - * - * a negative example, what's NOT working: - * key => [ value1, [inner_value1, inner_value2], value2] - * Since this isn't necessary for config values, there's no further logic handling such complex-list-in-list scenarios - * - * @param array $data A full config array - * - * @return string The config stream, which can be saved - */ - public static function encode(array $data): string - { - // Add the typical header values - $dataString = ' value, - * 'key' => value, - * ... - * ] - * - * @param array $config The associative/key-value array - * @param int $level The current level of recursion (necessary for tab-indentation calculation) - * @param bool $inList If true, the current array resides inside another list. Different rules may be applicable - * - * @return string The config string - */ - protected static function extractAssociativeArray(array $config, int $level = 0, bool $inList = false): string - { - $string = ''; - - // Because we're in a list, we have to add a line-break first - if ($inList) { - $string .= PHP_EOL . str_repeat("\t", $level); - } - - // Add a typical Line break for a taxative list of key-value pairs - $string .= '[' . PHP_EOL; - - foreach ($config as $configKey => $configValue) { - $string .= str_repeat("\t", $level + 1) . - "'$configKey' => " . - static::transformConfigValue($configValue, $level) . - ',' . PHP_EOL; - } - - $string .= str_repeat("\t", $level) . ']'; - - return $string; - } - - /** - * Extracts a list and save it into a string - * output1 - simple: - * [ value, value, value ] - * - * output2 - complex: - * [ - * [ value, value, value ], - * value, - * [ - * key => value, - * key => value, - * ], - * ] - * - * @param array $config The list - * @param int $level The current level of recursion (necessary for tab-indentation calculation) - * @param bool $inList If true, the current array resides inside another list. Different rules may be applicable - * - * @return string The config string - */ - protected static function extractList(array $config, int $level = 0, bool $inList = false): string - { - $string = '['; - - $countConfigValues = count($config); - // multiline defines, if each entry uses a new line - $multiline = false; - - // Search if any value is an array, because then other formatting rules are applicable - foreach ($config as $item) { - if (is_array($item)) { - $multiline = true; - break; - } - } - - for ($i = 0; $i < $countConfigValues; $i++) { - $isArray = is_array($config[$i]); - - /** - * In case this is an array in an array, directly extract this array again and continue - * Skip any other logic since this isn't applicable for an array in an array - */ - if ($isArray) { - $string .= PHP_EOL . str_repeat("\t", $level + 1); - $string .= static::extractArray($config[$i], $level + 1, $inList) . ','; - continue; - } - - if ($multiline) { - $string .= PHP_EOL . str_repeat("\t", $level + 1); - } - - $string .= static::transformConfigValue($config[$i], $level, true); - - // add trailing commas or whitespaces for certain config entries - if (($i < ($countConfigValues - 1))) { - $string .= ','; - if (!$multiline) { - $string .= ' '; - } - } - } - - // Add a new line for the last bracket as well - if ($multiline) { - $string .= PHP_EOL . str_repeat("\t", $level); - } - - $string .= ']'; - - return $string; - } - - /** - * Transforms one config value and returns the corresponding text-representation - * - * @param mixed $value Any value to transform - * @param int $level The current level of recursion (necessary for tab-indentation calculation) - * @param bool $inList If true, the current array resides inside another list. Different rules may be applicable - * - * @return string - */ - protected static function transformConfigValue($value, int $level = 0, bool $inList = false): string - { - switch (gettype($value)) { - case "boolean": - return ($value ? 'true' : 'false'); - case "integer": - case "double": - return $value; - case "string": - return sprintf('\'%s\'', addcslashes($value, '\'\\')); - case "array": - return static::extractArray($value, ++$level, $inList); - case "NULL": - return "null"; - case "object": - if (method_exists($value, '__toString')) { - return sprintf('\'%s\'', $value); - } elseif ($value instanceof \Serializable) { - try { - return $value->serialize(); - } catch (\Exception $e) { - throw new \InvalidArgumentException(sprintf('Cannot serialize %s.', gettype($value)), $e); - } - } else { - throw new \InvalidArgumentException(sprintf('%s is an object without stringify.', gettype($value))); - } - case "resource": - case "resource (closed)": - throw new \InvalidArgumentException(sprintf('%s in configs are not supported yet.', gettype($value))); - case "unknown type": - throw new \InvalidArgumentException(sprintf('%s is an unknown value', $value)); - default: - throw new \InvalidArgumentException(sprintf('%s is currently unsupported', $value)); - } - } -} diff --git a/src/Core/Config/Util/SerializeUtil.php b/src/Core/Config/Util/SerializeUtil.php new file mode 100644 index 000000000..0a886e230 --- /dev/null +++ b/src/Core/Config/Util/SerializeUtil.php @@ -0,0 +1,114 @@ +. + * + */ + +namespace Friendica\Core\Config\Util; + +/** + * Serialize utils + * + * Retrieved from https://github.com/WordPress/wordpress-develop/blob/6.1/src/wp-includes/functions.php + */ +class SerializeUtil +{ + public static function maybeUnserialize($value) + { + if (static::isSerialized($value)) { + return @unserialize(trim($value)); + } + + return $value; + } + + /** + * Checks value to find if it was serialized. + * + * If $data is not a string, then returned value will always be false. + * Serialized data is always a string. + * + * @param mixed $data Value to check to see if was serialized. + * @param bool $strict Optional. Whether to be strict about the end of the string. Default true. + * + * @return bool False if not serialized and true if it was. + * @since 6.1.0 Added Enum support. + * + * @since 2.0.5 + */ + public static function isSerialized($data, bool $strict = true): bool + { + // If it isn't a string, it isn't serialized. + if (!is_string($data)) { + return false; + } + $data = trim($data); + if ('N;' === $data) { + return true; + } + if (strlen($data) < 4) { + return false; + } + if (':' !== $data[1]) { + return false; + } + if ($strict) { + $lastc = substr($data, -1); + if (';' !== $lastc && '}' !== $lastc) { + return false; + } + } else { + $semicolon = strpos($data, ';'); + $brace = strpos($data, '}'); + // Either ; or } must exist. + if (false === $semicolon && false === $brace) { + return false; + } + // But neither must be in the first X characters. + if (false !== $semicolon && $semicolon < 3) { + return false; + } + if (false !== $brace && $brace < 4) { + return false; + } + } + $token = $data[0]; + switch ($token) { + case 's': + if ($strict) { + if ('"' !== substr($data, -2, 1)) { + return false; + } + } elseif (false === strpos($data, '"')) { + return false; + } + // Or else fall through. + // no break + case 'a': + case 'O': + case 'E': + return (bool)preg_match("/^{$token}:[0-9]+:/s", $data); + case 'b': + case 'i': + case 'd': + $end = $strict ? '$' : ''; + return (bool)preg_match("/^{$token}:[0-9.E+-]+;$end/", $data); + } + return false; + } +} diff --git a/src/Core/Logger/Type/StreamLogger.php b/src/Core/Logger/Type/StreamLogger.php index 6e0f65797..d56f5b20e 100644 --- a/src/Core/Logger/Type/StreamLogger.php +++ b/src/Core/Logger/Type/StreamLogger.php @@ -92,7 +92,7 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy $this->fileSystem = $fileSystem; $stream = $this->logfile ?? $config->get('system', 'logfile'); - if ((file_exists($stream) && !is_writable($stream)) || is_writable(basename($stream))) { + if ((@file_exists($stream) && !@is_writable($stream)) && !@is_writable(basename($stream))) { throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $stream)); } diff --git a/src/Core/Update.php b/src/Core/Update.php index cef72f34b..a4c5cf313 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -129,15 +129,26 @@ class Update DI::lock()->release('dbupdate', true); } + if (!DBStructure::existsTable('config')) { + DBA::e(<<get('system', 'build'); if (empty($build)) { - // legacy option - check if there's something in the Config table - if (DBStructure::existsTable('config')) { - $dbConfig = DBA::selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'build']); - if (!empty($dbConfig)) { - $build = $dbConfig['v']; - } + $dbConfig = DBA::selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'build']); + if (!empty($dbConfig)) { + $build = $dbConfig['v']; } if (empty($build) || ($build > DB_UPDATE_VERSION)) { diff --git a/src/DI.php b/src/DI.php index 6fd0e3a7a..24f06d279 100644 --- a/src/DI.php +++ b/src/DI.php @@ -37,9 +37,34 @@ abstract class DI /** @var Dice */ private static $dice; - public static function init(Dice $dice) + /** + * Initialize the singleton DI container with the Dice instance + * + * @param Dice $dice The Dice instance + * @param bool $disableDepByHand If true, the database dependencies aren't set, thus any occurrence of logging or + * profiling in database methods would lead to an error. This flag is for testing only. + * + * @return void + */ + public static function init(Dice $dice, bool $disableDepByHand = false) { self::$dice = $dice; + + if (!$disableDepByHand) { + self::setCompositeRootDependencyByHand(); + } + } + + /** + * I HATE this method, but everything else needs refactoring at the database itself + * Set the database dependencies manually, because of current, circular dependencies between the database and the config table + * + * @todo Instead of this madness, split the database in a core driver-dependent (mysql, mariadb, postgresql, ..) part without any other dependency unlike credentials and in the full-featured, driver-independent database class with all dependencies + */ + public static function setCompositeRootDependencyByHand() + { + $database = static::dba(); + $database->setDependency(static::config(), static::profiler(), static::logger()); } /** diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index 579b9cdc1..dc1e785a5 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -74,7 +74,7 @@ class DBStructure $old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data', 'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule', 'deliverq', 'dsprphotoq', 'ffinder', 'sign', 'spam', 'term', 'user-item', 'thread', 'item', 'challenge', - 'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation', 'fcontact', 'config', 'addon']; + 'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation', 'fcontact', 'addon']; $tables = DBA::selectToArray('INFORMATION_SCHEMA.TABLES', ['TABLE_NAME'], ['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']); diff --git a/src/Database/Database.php b/src/Database/Database.php index a5fe7f978..9527fefb0 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -36,6 +36,7 @@ use PDO; use PDOException; use PDOStatement; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; /** * This class is for the low level database stuff that does driver specific things. @@ -54,15 +55,15 @@ class Database /** * @var IManageConfigValues */ - protected $config; + protected $config = null; /** * @var Profiler */ - protected $profiler; + protected $profiler = null; /** * @var LoggerInterface */ - protected $logger; + protected $logger = null; protected $server_info = ''; /** @var PDO|mysqli */ protected $connection; @@ -80,18 +81,36 @@ class Database /** @var ViewDefinition */ protected $viewDefinition; - public function __construct(IManageConfigValues $config, Profiler $profiler, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition, LoggerInterface $logger) + public function __construct(IManageConfigValues $config, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition) { // We are storing these values for being able to perform a reconnect $this->config = $config; - $this->profiler = $profiler; $this->dbaDefinition = $dbaDefinition; $this->viewDefinition = $viewDefinition; - $this->logger = $logger; + + // Use dummy values - necessary for the first factory call of the logger itself + $this->logger = new NullLogger(); + $this->profiler = new Profiler($config); $this->connect(); } + /** + * @param IManageConfigValues $config + * @param Profiler $profiler + * @param LoggerInterface $logger + * + * @return void + * + * @todo Make this method obsolet - use a clean pattern instead ... + */ + public function setDependency(IManageConfigValues $config, Profiler $profiler, LoggerInterface $logger) + { + $this->logger = $logger; + $this->profiler = $profiler; + $this->config = $config; + } + /** * Tries to connect to database * diff --git a/src/Module/Admin/Summary.php b/src/Module/Admin/Summary.php index 9ca71e33d..b4ea14cf0 100644 --- a/src/Module/Admin/Summary.php +++ b/src/Module/Admin/Summary.php @@ -114,10 +114,6 @@ class Summary extends BaseAdmin $warningtext[] = DI::l10n()->t('Friendica\'s configuration now is stored in config/local.config.php, please copy config/local-sample.config.php and move your config from config/local.ini.php. See the Config help page for help with the transition.', DI::baseUrl()->get() . '/help/Config'); } - if (!DI::configFileManager()->dataIsWritable()) { - $warningtext[] = DI::l10n()->t('Friendica\'s configuration store "%s" isn\'t writable. Until then database updates won\'t be applied automatically, admin settings and console configuration changes won\'t be saved.', ConfigFileManager::CONFIG_DATA_FILE); - } - // Check server vitality if (!self::checkSelfHostMeta()) { $well_known = DI::baseUrl()->get() . Probe::HOST_META; diff --git a/src/Network/HTTPClient/Client/HttpClient.php b/src/Network/HTTPClient/Client/HttpClient.php index 4ee084215..31eb22be0 100644 --- a/src/Network/HTTPClient/Client/HttpClient.php +++ b/src/Network/HTTPClient/Client/HttpClient.php @@ -76,7 +76,7 @@ class HttpClient implements ICanSendHttpRequests if(!filter_var($host, FILTER_VALIDATE_IP) && !@dns_get_record($host . '.', DNS_A + DNS_AAAA) && !gethostbyname($host)) { $this->logger->debug('URL cannot be resolved.', ['url' => $url, 'callstack' => System::callstack(20)]); $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); + return CurlResult::createErrorCurl($this->logger, $url); } if (Network::isLocalLink($url)) { @@ -86,7 +86,7 @@ class HttpClient implements ICanSendHttpRequests if (strlen($url) > 1000) { $this->logger->debug('URL is longer than 1000 characters.', ['url' => $url, 'callstack' => System::callstack(20)]); $this->profiler->stopRecording(); - return CurlResult::createErrorCurl(substr($url, 0, 200)); + return CurlResult::createErrorCurl($this->logger, substr($url, 0, 200)); } $parts2 = []; @@ -105,7 +105,7 @@ class HttpClient implements ICanSendHttpRequests if (Network::isUrlBlocked($url)) { $this->logger->info('Domain is blocked.', ['url' => $url]); $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); + return CurlResult::createErrorCurl($this->logger, $url); } $conf = []; @@ -176,11 +176,11 @@ class HttpClient implements ICanSendHttpRequests $exception->hasResponse()) { return new GuzzleResponse($exception->getResponse(), $url, $exception->getCode(), ''); } else { - return new CurlResult($url, '', ['http_code' => 500], $exception->getCode(), ''); + return new CurlResult($this->logger, $url, '', ['http_code' => 500], $exception->getCode(), ''); } } catch (InvalidArgumentException | \InvalidArgumentException $argumentException) { $this->logger->info('Invalid Argument for HTTP call.', ['url' => $url, 'method' => $method, 'exception' => $argumentException]); - return new CurlResult($url, '', ['http_code' => 500], $argumentException->getCode(), $argumentException->getMessage()); + return new CurlResult($this->logger, $url, '', ['http_code' => 500], $argumentException->getCode(), $argumentException->getMessage()); } finally { unlink($conf['sink']); $this->logger->debug('Request stop.', ['url' => $url, 'method' => $method]); diff --git a/src/Network/HTTPClient/Response/CurlResult.php b/src/Network/HTTPClient/Response/CurlResult.php index 044eb1857..f187fb62a 100644 --- a/src/Network/HTTPClient/Response/CurlResult.php +++ b/src/Network/HTTPClient/Response/CurlResult.php @@ -25,6 +25,7 @@ use Friendica\Core\Logger; use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses; use Friendica\Network\HTTPException\UnprocessableEntityException; use Friendica\Util\Network; +use Psr\Log\LoggerInterface; /** * A content class for Curl call results @@ -96,6 +97,11 @@ class CurlResult implements ICanHandleHttpResponses */ private $error; + /** + * @var LoggerInterface + */ + protected $logger; + /** * Creates an errored CURL response * @@ -104,9 +110,9 @@ class CurlResult implements ICanHandleHttpResponses * @return ICanHandleHttpResponses a CURL with error response * @throws UnprocessableEntityException */ - public static function createErrorCurl(string $url = '') + public static function createErrorCurl(LoggerInterface $logger, string $url = '') { - return new CurlResult($url, '', ['http_code' => 0]); + return new CurlResult($logger, $url, '', ['http_code' => 0]); } /** @@ -120,8 +126,10 @@ class CurlResult implements ICanHandleHttpResponses * * @throws UnprocessableEntityException when HTTP code of the CURL response is missing */ - public function __construct(string $url, string $result, array $info, int $errorNumber = 0, string $error = '') + public function __construct(LoggerInterface $logger, string $url, string $result, array $info, int $errorNumber = 0, string $error = '') { + $this->logger = $logger; + if (!array_key_exists('http_code', $info)) { throw new UnprocessableEntityException('CURL response doesn\'t contains a response HTTP code'); } @@ -132,7 +140,7 @@ class CurlResult implements ICanHandleHttpResponses $this->errorNumber = $errorNumber; $this->error = $error; - Logger::debug('construct', ['url' => $url, 'returncode' => $this->returnCode, 'result' => $result]); + $this->logger->debug('construct', ['url' => $url, 'returncode' => $this->returnCode, 'result' => $result]); $this->parseBodyHeader($result); $this->checkSuccess(); @@ -172,7 +180,7 @@ class CurlResult implements ICanHandleHttpResponses } if (!$this->isSuccess) { - Logger::debug('debug', ['info' => $this->info]); + $this->logger->debug('debug', ['info' => $this->info]); } if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) { diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 9dfd7820c..dcab1d0c8 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1513); + define('DB_UPDATE_VERSION', 1514); } return [ @@ -554,6 +554,19 @@ return [ "k_expires" => ["k", "expires"], ] ], + "config" => [ + "comment" => "main configuration storage", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => ""], + "cat" => ["type" => "varbinary(50)", "not null" => "1", "default" => "", "comment" => "The category of the entry"], + "k" => ["type" => "varbinary(50)", "not null" => "1", "default" => "", "comment" => "The key of the entry"], + "v" => ["type" => "mediumtext", "comment" => ""], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "cat_k" => ["UNIQUE", "cat", "k"], + ] + ], "contact-relation" => [ "comment" => "Contact relations", "fields" => [ diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 5b7a34568..42f193ba5 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -108,7 +108,7 @@ return [ ], ], Config\Capability\IManageConfigValues::class => [ - 'instanceOf' => Config\Model\Config::class, + 'instanceOf' => Config\Model\DatabaseConfig::class, 'constructParams' => [ $_SERVER, ], @@ -137,7 +137,7 @@ return [ ], Database::class => [ 'constructParams' => [ - [Dice::INSTANCE => \Psr\Log\NullLogger::class], + [Dice::INSTANCE => Config\Model\ReadOnlyFileConfig::class], ], ], /** diff --git a/tests/DatabaseTestTrait.php b/tests/DatabaseTestTrait.php index cf03da29f..4badf85e9 100644 --- a/tests/DatabaseTestTrait.php +++ b/tests/DatabaseTestTrait.php @@ -53,16 +53,14 @@ trait DatabaseTestTrait /** * Loads a given DB fixture for this DB test * - * @param string $fixture The path to the fixture + * @param string[][] $fixture The fixture array * @param Database $dba The DB connection * * @throws \Exception */ - protected function loadFixture(string $fixture, Database $dba) + protected function loadDirectFixture(array $fixture, Database $dba) { - $data = include $fixture; - - foreach ($data as $tableName => $rows) { + foreach ($fixture as $tableName => $rows) { if (is_numeric($tableName)) { continue; } @@ -77,4 +75,19 @@ trait DatabaseTestTrait } } } + + /** + * Loads a given DB fixture-file for this DB test + * + * @param string $fixture The path to the fixture + * @param Database $dba The DB connection + * + * @throws \Exception + */ + protected function loadFixture(string $fixture, Database $dba) + { + $data = include $fixture; + + $this->loadDirectFixture($data, $dba); + } } diff --git a/tests/Util/CreateDatabaseTrait.php b/tests/Util/CreateDatabaseTrait.php index 127d8da2f..3d8330653 100644 --- a/tests/Util/CreateDatabaseTrait.php +++ b/tests/Util/CreateDatabaseTrait.php @@ -21,7 +21,7 @@ namespace Friendica\Test\Util; -use Friendica\Core\Config\Model\Config; +use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Core\Config\ValueObject\Cache; use Friendica\Database\Database; @@ -37,16 +37,23 @@ trait CreateDatabaseTrait use DatabaseTestTrait; use VFSTrait; + /** @var Database|null */ + protected $dba = null; + public function getDbInstance(): Database { + if (isset($this->dba)) { + return $this->dba; + } + $configFileManager = new ConfigFileManager($this->root->url(), $this->root->url() . '/config/', $this->root->url() . '/static/'); - $config = new Config($configFileManager, new Cache([ + $config = new ReadOnlyFileConfig(new Cache([ 'database' => [ 'disable_pdo' => true ], ])); - $database = new StaticDatabase($config, new Profiler($config), (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load(), new NullLogger()); + $database = new StaticDatabase($config, (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load()); $database->setTestmode(true); return $database; diff --git a/tests/Util/VFSTrait.php b/tests/Util/VFSTrait.php index e36cc1234..a98b7925d 100644 --- a/tests/Util/VFSTrait.php +++ b/tests/Util/VFSTrait.php @@ -49,45 +49,38 @@ trait VFSTrait // create a virtual directory and copy all needed files and folders to it $this->root = vfsStream::setup('friendica', 0777, $structure); - $this->setConfigFile('dbstructure.config.php', true); - $this->setConfigFile('dbview.config.php', true); - $this->setConfigFile('defaults.config.php', true); - $this->setConfigFile('settings.config.php', true); - $this->setConfigFile('local.config.php'); - $this->setDataFile('node.config.php'); - } - - protected function setDataFile(string $filename) - { - $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config' . DIRECTORY_SEPARATOR . - $filename; - - if (file_exists($file)) { - vfsStream::newFile($filename) - ->at($this->root->getChild('config')) - ->setContent(file_get_contents($file)); - } + $this->setConfigFile('static' . DIRECTORY_SEPARATOR . 'dbstructure.config.php', true); + $this->setConfigFile('static' . DIRECTORY_SEPARATOR . 'dbview.config.php', true); + $this->setConfigFile('static' . DIRECTORY_SEPARATOR . 'defaults.config.php', true); + $this->setConfigFile('static' . DIRECTORY_SEPARATOR . 'settings.config.php', true); + $this->setConfigFile( + 'mods' . DIRECTORY_SEPARATOR . 'local.config.ci.php', + false, 'local.config.php' + ); } /** * Copying a config file from the file system to the Virtual File System * - * @param string $filename The filename of the config file - * @param bool $static True, if the folder `static` instead of `config` should be used + * @param string $sourceFilePath The filename of the config file + * @param bool $static True, if the folder `static` instead of `config` should be used */ - protected function setConfigFile(string $filename, bool $static = false) + protected function setConfigFile(string $sourceFilePath, bool $static = false, string $targetFileName = null) { $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . - ($static ? 'static' : 'config') . DIRECTORY_SEPARATOR . - $filename; + $sourceFilePath; if (file_exists($file)) { - vfsStream::newFile($filename) + if (empty($targetFileName)) { + $tmpArray = preg_split('/\\' . DIRECTORY_SEPARATOR . '/', $sourceFilePath); + $targetFileName = array_pop($tmpArray); + } + vfsStream::newFile($targetFileName) ->at($this->root->getChild(($static ? 'static' : 'config'))) ->setContent(file_get_contents($file)); + } else { + throw new \Exception(sprintf('Unexpected missing config \'%s\'', $file)); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7dc3c9868..9f7c1c3f9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,8 +20,6 @@ * This file is loaded by PHPUnit before any test. */ -use Dice\Dice; -use Friendica\DI; use PHPUnit\Framework\TestCase; if (!file_exists(__DIR__ . '/../vendor/autoload.php')) { @@ -34,8 +32,3 @@ require __DIR__ . '/../vendor/autoload.php'; if (!class_exists(TestCase::class)) { class_alias(\PHPUnit\Framework\TestCase::class, TestCase::class); } - -$dice = new Dice(); -$dice = $dice->addRules(include __DIR__ . '/../static/dependencies.config.php'); - -DI::init($dice); diff --git a/tests/src/Console/ServerBlockConsoleTest.php b/tests/src/Console/ServerBlockConsoleTest.php index 2ea1a6669..e8d75863c 100644 --- a/tests/src/Console/ServerBlockConsoleTest.php +++ b/tests/src/Console/ServerBlockConsoleTest.php @@ -21,8 +21,10 @@ namespace Friendica\Test\src\Console; +use Dice\Dice; use Friendica\Console\ServerBlock; use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\DI; use Friendica\Moderation\DomainPatternBlocklist; use Mockery; @@ -78,6 +80,11 @@ CONS; */ public function testAddBlockedServer() { + $dice = new Dice(); + $dice = $dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php'); + + DI::init($dice, true); + $this->blocklistMock ->shouldReceive('addPattern') ->with('testme.now', 'I like it!') @@ -98,6 +105,11 @@ CONS; */ public function testUpdateBlockedServer() { + $dice = new Dice(); + $dice = $dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php'); + + DI::init($dice, true); + $this->blocklistMock ->shouldReceive('addPattern') ->with('pod.ordoevangelistarum.com', 'Other reason') @@ -118,6 +130,11 @@ CONS; */ public function testRemoveBlockedServer() { + $dice = new Dice(); + $dice = $dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php'); + + DI::init($dice, true); + $this->blocklistMock ->shouldReceive('removePattern') ->with('pod.ordoevangelistarum.com') diff --git a/tests/src/Core/Cache/DatabaseCacheTest.php b/tests/src/Core/Cache/DatabaseCacheTest.php index 81804a806..98afa9e24 100644 --- a/tests/src/Core/Cache/DatabaseCacheTest.php +++ b/tests/src/Core/Cache/DatabaseCacheTest.php @@ -22,19 +22,14 @@ namespace Friendica\Test\src\Core\Cache; use Friendica\Core\Cache; -use Friendica\Core\Config\Factory\Config; -use Friendica\Database\Definition\DbaDefinition; -use Friendica\Database\Definition\ViewDefinition; use Friendica\Test\DatabaseTestTrait; -use Friendica\Test\Util\Database\StaticDatabase; +use Friendica\Test\Util\CreateDatabaseTrait; use Friendica\Test\Util\VFSTrait; -use Friendica\Util\Profiler; -use Mockery; -use Psr\Log\NullLogger; class DatabaseCacheTest extends CacheTest { use DatabaseTestTrait; + use CreateDatabaseTrait; use VFSTrait; protected function setUp(): void @@ -48,23 +43,7 @@ class DatabaseCacheTest extends CacheTest protected function getInstance() { - $profiler = Mockery::mock(Profiler::class); - $profiler->shouldReceive('startRecording'); - $profiler->shouldReceive('stopRecording'); - $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true); - - // load real config to avoid mocking every config-entry which is related to the Database class - $configFactory = new Config(); - $configFileManager = (new Config())->createConfigFileManager($this->root->url(), []); - $configCache = $configFactory->createCache($configFileManager); - $config = new \Friendica\Core\Config\Model\Config($configFileManager, $configCache); - - $dbaDefinition = (new DbaDefinition($configCache->get('system', 'basepath')))->load(); - $viewDefinition = (new ViewDefinition($configCache->get('system', 'basepath')))->load(); - - $dba = new StaticDatabase($config, $profiler, $dbaDefinition, $viewDefinition, new NullLogger()); - - $this->cache = new Cache\Type\DatabaseCache('database', $dba); + $this->cache = new Cache\Type\DatabaseCache('database', $this->getDbInstance()); return $this->cache; } diff --git a/tests/src/Core/Config/Cache/ConfigFileManagerTest.php b/tests/src/Core/Config/Cache/ConfigFileManagerTest.php index e5b630a20..1385c8b19 100644 --- a/tests/src/Core/Config/Cache/ConfigFileManagerTest.php +++ b/tests/src/Core/Config/Cache/ConfigFileManagerTest.php @@ -392,91 +392,6 @@ class ConfigFileManagerTest extends MockedTest self::assertEquals('newValue', $configCache->get('system', 'newKey')); } - public function testSaveData() - { - $this->delConfigFile('local.config.php'); - - $fileDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config' . DIRECTORY_SEPARATOR; - - vfsStream::newFile('B.config.php') - ->at($this->root->getChild('config2')) - ->setContent(file_get_contents($fileDir . 'B.config.php')); - - $configFileManager = (new Config())->createConfigFileManager($this->root->url(), - [ - 'FRIENDICA_CONFIG_DIR' => $this->root->getChild('config2')->url(), - ]); - $configCache = new Cache(); - - $configFileManager->setupCache($configCache); - - $specialChars = '!"§$%&/()(/&%$\'>set('system', 'test', 'it', Cache::SOURCE_DATA); - $configCache->set('config', 'test', 'it', Cache::SOURCE_DATA); - $configCache->set('system', 'test_2', 2, Cache::SOURCE_DATA); - $configCache->set('special_chars', 'special', $specialChars, Cache::SOURCE_DATA); - $configFileManager->saveData($configCache); - - // Reload the configCache with the new values - $configCache2 = new Cache(); - $configFileManager->setupCache($configCache2); - - self::assertEquals($configCache, $configCache2); - self::assertEquals([ - 'system' => [ - 'test' => 'it', - 'test_2' => 2 - ], - 'config' => [ - 'test' => 'it', - ], - 'special_chars' => [ - 'special' => $specialChars, - ]], $configCache2->getDataBySource(Cache::SOURCE_DATA)); - } - - /** - * If we delete something with the Cache::delete() functionality, be sure to probably reset it to the underlying key - */ - public function testDeleteKeyOverwrite() - { - $this->delConfigFile('node.config.php'); - - $fileDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config' . DIRECTORY_SEPARATOR; - - vfsStream::newFile('B.config.php') - ->at($this->root->getChild('config')) - ->setContent(file_get_contents($fileDir . 'B.config.php')); - - $configFileManager = (new Config())->createConfigFileManager($this->root->url()); - $configCache = new Cache(); - - $configFileManager->setupCache($configCache); - - $configCache->delete('system', 'default_timezone'); - - $configFileManager->saveData($configCache); - - // assert that system.default_timezone is now the restored 'UTC' from the defaults - $configCache = new Cache(); - - $configFileManager->setupCache($configCache); - - self::assertEquals('UTC', $configCache->get('system', 'default_timezone')); - } - /** * Test for empty node.config.php */ diff --git a/tests/src/Core/Config/ConfigTest.php b/tests/src/Core/Config/ConfigTest.php index f24404474..643c73754 100644 --- a/tests/src/Core/Config/ConfigTest.php +++ b/tests/src/Core/Config/ConfigTest.php @@ -23,18 +23,20 @@ namespace Friendica\Test\src\Core\Config; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Config\Model\Config; +use Friendica\Core\Config\Model\DatabaseConfig; +use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\Util\ConfigFileManager; -use Friendica\Core\Config\Util\ConfigFileTransformer; use Friendica\Core\Config\ValueObject\Cache; -use Friendica\Test\MockedTest; +use Friendica\Test\DatabaseTest; +use Friendica\Test\Util\CreateDatabaseTrait; use Friendica\Test\Util\VFSTrait; use org\bovigo\vfs\vfsStream; -class ConfigTest extends MockedTest +class ConfigTest extends DatabaseTest { use ArraySubsetAsserts; use VFSTrait; + use CreateDatabaseTrait; /** @var Cache */ protected $configCache; @@ -77,7 +79,7 @@ class ConfigTest extends MockedTest public function getInstance() { $this->configFileManager->setupCache($this->configCache); - return new Config($this->configFileManager, $this->configCache); + return new DatabaseConfig($this->getDbInstance(), $this->configCache); } public function dataTests() @@ -162,15 +164,30 @@ class ConfigTest extends MockedTest ]; } + public function configToDbArray(array $config): array + { + $dbarray = []; + + foreach ($config as $category => $data) { + foreach ($data as $key => $value) { + $dbarray[] = [ + 'cat' => $category, + 'k' => $key, + 'v' => $value, + ]; + } + } + + return ['config' => $dbarray]; + } + /** * Test the configuration initialization * @dataProvider dataConfigLoad */ public function testSetUp(array $data) { - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode($data)); + $this->loadDirectFixture($this->configToDbArray($data) , $this->getDbInstance()); $this->testedConfig = $this->getInstance(); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); @@ -189,9 +206,7 @@ class ConfigTest extends MockedTest */ public function testReload(array $data, array $load) { - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode($data)); + $this->loadDirectFixture($this->configToDbArray($data), $this->getDbInstance()); $this->testedConfig = $this->getInstance(); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); @@ -274,9 +289,7 @@ class ConfigTest extends MockedTest */ public function testCacheLoadDouble(array $data1, array $data2, array $expect = []) { - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode($data1)); + $this->loadDirectFixture($this->configToDbArray($data1), $this->getDbInstance()); $this->testedConfig = $this->getInstance(); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); @@ -286,9 +299,7 @@ class ConfigTest extends MockedTest self::assertConfig($cat, $data); } - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode($data2)); + $this->loadDirectFixture($this->configToDbArray($data2), $this->getDbInstance()); $this->testedConfig->reload(); @@ -302,7 +313,7 @@ class ConfigTest extends MockedTest */ public function testLoadWrong() { - $this->testedConfig = new Config($this->configFileManager, new Cache()); + $this->testedConfig = new ReadOnlyFileConfig(new Cache()); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); self::assertEmpty($this->testedConfig->getCache()->getAll()); @@ -354,7 +365,7 @@ class ConfigTest extends MockedTest { $this->configCache->load(['test' => ['it' => $data]], Cache::SOURCE_FILE); - $this->testedConfig = new Config($this->configFileManager, $this->configCache); + $this->testedConfig = new DatabaseConfig($this->getDbInstance(), $this->configCache); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); self::assertEquals($data, $this->testedConfig->get('test', 'it')); @@ -386,19 +397,15 @@ class ConfigTest extends MockedTest */ public function testSetGetLowPrio() { - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode([ - 'config' => ['test' => 'it'], - ])); + $this->loadDirectFixture(['config' => [['cat' => 'config', 'k' => 'test', 'v' => 'it']]], $this->getDbInstance()); $this->testedConfig = $this->getInstance(); self::assertInstanceOf(Cache::class, $this->testedConfig->getCache()); self::assertEquals('it', $this->testedConfig->get('config', 'test')); $this->testedConfig->getCache()->set('config', 'test', 'prio', Cache::SOURCE_ENV); - // now you have to get the env variable entry as output, even with a new set (which failed) and a get refresh - self::assertFalse($this->testedConfig->set('config', 'test', '123')); + // You can set a config value, but if there's a value with a higher priority (environment), this value will persist when retrieving + self::assertTrue($this->testedConfig->set('config', 'test', '123')); self::assertEquals('prio', $this->testedConfig->get('config', 'test', '', true)); } @@ -526,26 +533,8 @@ class ConfigTest extends MockedTest public function testGetCategory(array $data, string $category, array $assertion) { $this->configCache = new Cache($data); - $config = new Config($this->configFileManager, $this->configCache); + $config = new ReadOnlyFileConfig($this->configCache); self::assertEquals($assertion, $config->get($category)); } - - /** - * Tests, if an overwritten value of an existing key will reset to it's default after deletion - */ - public function testDeleteReturnsDefault() - { - vfsStream::newFile(ConfigFileManager::CONFIG_DATA_FILE) - ->at($this->root->getChild('config')) - ->setContent(ConfigFileTransformer::encode([ - 'config' => ['sitename' => 'overritten'], - ])); - - $config = $this->getInstance(); - self::assertEquals('overritten', $config->get('config', 'sitename')); - - $config->delete('config', 'sitename'); - self::assertEquals('Friendica Social Network', $config->get('config', 'sitename')); - } } diff --git a/tests/src/Core/Config/ConfigTransactionTest.php b/tests/src/Core/Config/ConfigTransactionTest.php index c6eb7e268..30e6d9885 100644 --- a/tests/src/Core/Config/ConfigTransactionTest.php +++ b/tests/src/Core/Config/ConfigTransactionTest.php @@ -22,18 +22,21 @@ namespace Friendica\Test\src\Core\Config; use Friendica\Core\Config\Capability\ISetConfigValuesTransactionally; -use Friendica\Core\Config\Model\Config; +use Friendica\Core\Config\Model\DatabaseConfig; +use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\Model\ConfigTransaction; use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Core\Config\ValueObject\Cache; +use Friendica\Database\Database; +use Friendica\Test\DatabaseTest; +use Friendica\Test\FixtureTest; use Friendica\Test\MockedTest; +use Friendica\Test\Util\Database\StaticDatabase; use Friendica\Test\Util\VFSTrait; use Mockery\Exception\InvalidCountException; -class ConfigTransactionTest extends MockedTest +class ConfigTransactionTest extends FixtureTest { - use VFSTrait; - /** @var ConfigFileManager */ protected $configFileManager; @@ -41,8 +44,6 @@ class ConfigTransactionTest extends MockedTest { parent::setUp(); - $this->setUpVfsDir(); - $this->configFileManager = new ConfigFileManager($this->root->url(), $this->root->url() . '/config/', $this->root->url() . '/static/'); } @@ -57,7 +58,7 @@ class ConfigTransactionTest extends MockedTest public function testInstance() { - $config = new Config($this->configFileManager, new Cache()); + $config = new DatabaseConfig($this->dice->create(Database::class), new Cache()); $configTransaction = new ConfigTransaction($config); self::assertInstanceOf(ISetConfigValuesTransactionally::class, $configTransaction); @@ -66,17 +67,13 @@ class ConfigTransactionTest extends MockedTest public function testConfigTransaction() { - $config = new Config($this->configFileManager, new Cache()); + $config = new DatabaseConfig($this->dice->create(Database::class), new Cache()); $config->set('config', 'key1', 'value1'); $config->set('system', 'key2', 'value2'); $config->set('system', 'keyDel', 'valueDel'); $config->set('delete', 'keyDel', 'catDel'); $configTransaction = new ConfigTransaction($config); - // 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 $configTransaction->set('transaction', 'key3', 'value3'); @@ -93,11 +90,6 @@ class ConfigTransactionTest extends MockedTest self::assertEquals('valueDel', $config->get('system', 'keyDel')); self::assertEquals('catDel', $config->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! $configTransaction->commit(); @@ -107,12 +99,6 @@ class ConfigTransactionTest extends MockedTest self::assertEquals('value3', $config->get('transaction', 'key3')); self::assertNull($config->get('system', 'keyDel')); self::assertNull($config->get('delete', '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); } @@ -124,7 +110,7 @@ class ConfigTransactionTest extends MockedTest { $this->configFileManager = \Mockery::spy(ConfigFileManager::class); - $config = new Config($this->configFileManager, new Cache()); + $config = new DatabaseConfig($this->dice->create(Database::class), new Cache()); $configTransaction = new ConfigTransaction($config); // commit empty transaction diff --git a/tests/src/Core/Config/Util/ConfigFileTransformerTest.php b/tests/src/Core/Config/Util/ConfigFileTransformerTest.php deleted file mode 100644 index c11a770ad..000000000 --- a/tests/src/Core/Config/Util/ConfigFileTransformerTest.php +++ /dev/null @@ -1,110 +0,0 @@ -. - * - */ - -namespace Friendica\Test\src\Core\Config\Util; - -use Friendica\Core\Config\Util\ConfigFileTransformer; -use Friendica\Test\MockedTest; -use Friendica\Test\Util\SerializableObjectDouble; -use ParagonIE\HiddenString\HiddenString; -use function PHPUnit\Framework\assertEquals; - -class ConfigFileTransformerTest extends MockedTest -{ - public function dataTests() - { - return [ - 'default' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/A.node.config.php'), - ], - 'extended' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/B.node.config.php'), - ], - 'friendica.local' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/C.node.config.php'), - ], - 'friendica.local.2' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/D.node.config.php'), - ], - 'object_invalid' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/object.node.config.php'), - 'assertException' => true, - ], - 'resource' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/ressource.node.config.php'), - 'assertException' => false, - 'assertion' => << [ - 'ressources_not_allowed' => '', - ], -]; - -EOF, - ], - 'object_valid' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/object_valid.node.config.php'), - 'assertException' => false, - 'assertion' => << [ - 'toString' => 'test', - 'serializable' => 'serialized', - ], -]; - -EOF, - ], - 'test_types' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/types.node.config.php'), - ], - 'small_types' => [ - 'configFile' => (dirname(__DIR__, 4) . '/datasets/config/transformer/small_types.node.config.php'), - ] - ]; - } - - /** - * Tests if the given config will be decoded into an array and encoded into the same string again - * - * @dataProvider dataTests - */ - public function testConfigFile(string $configFile, bool $assertException = false, $assertion = null) - { - $dataArray = include $configFile; - - if ($assertException) { - self::expectException(\InvalidArgumentException::class); - } - - $newConfig = ConfigFileTransformer::encode($dataArray); - - if (empty($assertion)) { - self::assertEquals(file_get_contents($configFile), $newConfig); - } else { - self::assertEquals($assertion, $newConfig); - } - } -} diff --git a/tests/src/Core/InstallerTest.php b/tests/src/Core/InstallerTest.php index 415b1d3e8..376703a27 100644 --- a/tests/src/Core/InstallerTest.php +++ b/tests/src/Core/InstallerTest.php @@ -63,7 +63,7 @@ class InstallerTest extends MockedTest ->with(L10n::class) ->andReturn($this->l10nMock); - DI::init($this->dice); + DI::init($this->dice, true); } public static function tearDownAfterClass(): void @@ -361,7 +361,7 @@ class InstallerTest extends MockedTest ->with(ICanSendHttpRequests::class) ->andReturn($networkMock); - DI::init($this->dice); + DI::init($this->dice, true); // Mocking that we can use CURL $this->setFunctions(['curl_init' => true]); @@ -408,7 +408,7 @@ class InstallerTest extends MockedTest ->with(ICanSendHttpRequests::class) ->andReturn($networkMock); - DI::init($this->dice); + DI::init($this->dice, true); // Mocking that we can use CURL $this->setFunctions(['curl_init' => true]); diff --git a/tests/src/Core/Lock/SemaphoreLockTest.php b/tests/src/Core/Lock/SemaphoreLockTest.php index 6473935ca..41e73891e 100644 --- a/tests/src/Core/Lock/SemaphoreLockTest.php +++ b/tests/src/Core/Lock/SemaphoreLockTest.php @@ -24,11 +24,8 @@ namespace Friendica\Test\src\Core\Lock; use Dice\Dice; use Friendica\App; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Config\Model\Config; -use Friendica\Core\Config\Type\JitConfig; -use Friendica\Core\Config\Util\ConfigFileManager; +use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\ValueObject\Cache; -use Friendica\Core\Lock\Type\SemaphoreLock; use Friendica\Core\System; use Friendica\DI; use Mockery; @@ -46,11 +43,11 @@ class SemaphoreLockTest extends LockTest $dice->shouldReceive('create')->with(App::class)->andReturn($app); $configCache = new Cache(['system' => ['temppath' => '/tmp']]); - $configMock = new Config(Mockery::mock(ConfigFileManager::class), $configCache); + $configMock = new ReadOnlyFileConfig($configCache); $dice->shouldReceive('create')->with(IManageConfigValues::class)->andReturn($configMock); // @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject - DI::init($dice); + DI::init($dice, true); parent::setUp(); } diff --git a/tests/src/Core/Storage/Repository/StorageManagerTest.php b/tests/src/Core/Storage/Repository/StorageManagerTest.php index 7a976832f..446e93a7a 100644 --- a/tests/src/Core/Storage/Repository/StorageManagerTest.php +++ b/tests/src/Core/Storage/Repository/StorageManagerTest.php @@ -75,19 +75,19 @@ class StorageManagerTest extends DatabaseTest vfsStream::newDirectory(Type\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root); $this->logger = new NullLogger(); + $this->database = $this->getDbInstance(); $configFactory = new Config(); $configFileManager = $configFactory->createConfigFileManager($this->root->url()); $configCache = $configFactory->createCache($configFileManager); - $this->config = new \Friendica\Core\Config\Model\Config($configFileManager, $configCache); + $this->config = new \Friendica\Core\Config\Model\DatabaseConfig($this->database, $configCache); $this->config->set('storage', 'name', 'Database'); $this->config->set('storage', 'filesystem_path', $this->root->getChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER) ->url()); $this->l10n = \Mockery::mock(L10n::class); - $this->database = $this->getDbInstance(); } protected function tearDown(): void diff --git a/tests/src/Core/SystemTest.php b/tests/src/Core/SystemTest.php index eaa704d9d..9cfbe052c 100644 --- a/tests/src/Core/SystemTest.php +++ b/tests/src/Core/SystemTest.php @@ -36,7 +36,7 @@ class SystemTest extends TestCase $dice = \Mockery::mock(Dice::class); $dice->shouldReceive('create')->with(BaseURL::class)->andReturn($baseUrl); - DI::init($dice); + DI::init($dice, true); } private function assertGuid($guid, $length, $prefix = '') diff --git a/tests/src/Model/UserTest.php b/tests/src/Model/UserTest.php index b2b9bba92..f1785f99d 100644 --- a/tests/src/Model/UserTest.php +++ b/tests/src/Model/UserTest.php @@ -47,7 +47,7 @@ class UserTest extends MockedTest /** @var Dice|MockInterface $diceMock */ $diceMock = $diceMock->addRules(include __DIR__ . '/../../../static/dependencies.config.php'); $diceMock->shouldReceive('create')->withArgs([Database::class])->andReturn($this->dbMock); - DI::init($diceMock); + DI::init($diceMock, true); $this->parent = [ 'uid' => 1, diff --git a/tests/src/Module/Api/GnuSocial/GnuSocial/ConfigTest.php b/tests/src/Module/Api/GnuSocial/GnuSocial/ConfigTest.php index 0d0afa64b..6311175d3 100644 --- a/tests/src/Module/Api/GnuSocial/GnuSocial/ConfigTest.php +++ b/tests/src/Module/Api/GnuSocial/GnuSocial/ConfigTest.php @@ -41,12 +41,12 @@ class ConfigTest extends ApiTest ->run($this->httpExceptionMock); $json = $this->toJson($response); - self::assertEquals('localhost', $json->site->server); - self::assertEquals('frio', $json->site->theme); + self::assertEquals(DI::config()->get('config', 'hostname'), $json->site->server); + self::assertEquals(DI::config()->get('system', 'theme'), $json->site->theme); self::assertEquals(DI::baseUrl() . '/images/friendica-64.png', $json->site->logo); self::assertTrue($json->site->fancy); - self::assertEquals('en', $json->site->language); - self::assertEquals('UTC', $json->site->timezone); + self::assertEquals(DI::config()->get('system', 'language'), $json->site->language); + self::assertEquals(DI::config()->get('system', 'default_timezone'), $json->site->timezone); self::assertEquals(200000, $json->site->textlimit); self::assertFalse($json->site->private); self::assertEquals('always', $json->site->ssl); diff --git a/tests/src/Network/HTTPClient/Response/CurlResultTest.php b/tests/src/Network/HTTPClient/Response/CurlResultTest.php index 4cd899062..e5047d6af 100644 --- a/tests/src/Network/HTTPClient/Response/CurlResultTest.php +++ b/tests/src/Network/HTTPClient/Response/CurlResultTest.php @@ -21,32 +21,12 @@ namespace Friendica\Test\src\Network\HTTPClient\Response; -use Dice\Dice; -use Friendica\DI; use Friendica\Network\HTTPClient\Response\CurlResult; -use Mockery\MockInterface; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; class CurlResultTest extends TestCase { - protected function setUp(): void - { - parent::setUp(); - - /** @var Dice|MockInterface $dice */ - $dice = \Mockery::mock(Dice::class)->makePartial(); - $dice = $dice->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php'); - - $logger = new NullLogger(); - $dice->shouldReceive('create') - ->with(LoggerInterface::class) - ->andReturn($logger); - - DI::init($dice); - } - /** * @small */ @@ -57,7 +37,7 @@ class CurlResultTest extends TestCase $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [ + $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult(new NullLogger(),'https://test.local', $header . $body, [ 'http_code' => 200, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local' @@ -85,7 +65,7 @@ class CurlResultTest extends TestCase $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local/test/it', $header . $body, [ + $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult(new NullLogger(),'https://test.local/test/it', $header . $body, [ 'http_code' => 301, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local/test/it', @@ -112,7 +92,7 @@ class CurlResultTest extends TestCase $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local/test/it', $header . $body, [ + $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult(new NullLogger(),'https://test.local/test/it', $header . $body, [ 'http_code' => 500, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local/test/it', @@ -141,7 +121,7 @@ class CurlResultTest extends TestCase $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new CurlResult('https://test.local/test/it?key=value', $header . $body, [ + $curlResult = new CurlResult(new NullLogger(),'https://test.local/test/it?key=value', $header . $body, [ 'http_code' => 301, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local/test/it?key=value', @@ -165,7 +145,7 @@ class CurlResultTest extends TestCase $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head'); $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [ + $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult(new NullLogger(),'https://test.local', $header . $body, [ 'http_code' => 200, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local' @@ -182,7 +162,7 @@ class CurlResultTest extends TestCase $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head'); $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [ + $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult(new NullLogger(), 'https://test.local', $header . $body, [ 'http_code' => 200, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local' @@ -202,7 +182,7 @@ class CurlResultTest extends TestCase $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head'); $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body'); - $curlResult = new CurlResult('https://test.local', $header . $body, [ + $curlResult = new CurlResult(new NullLogger(),'https://test.local', $header . $body, [ 'http_code' => 200, 'content_type' => 'text/html; charset=utf-8', 'url' => 'https://test.local' diff --git a/tests/src/Util/BaseURLTest.php b/tests/src/Util/BaseURLTest.php index 4095c344d..a72ffc607 100644 --- a/tests/src/Util/BaseURLTest.php +++ b/tests/src/Util/BaseURLTest.php @@ -23,15 +23,18 @@ namespace Friendica\Test\src\Util; use Friendica\App\BaseURL; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Config\Model\Config; +use Friendica\Core\Config\Model\DatabaseConfig; +use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Core\Config\ValueObject\Cache; -use Friendica\Test\MockedTest; +use Friendica\Test\DatabaseTest; +use Friendica\Test\Util\CreateDatabaseTrait; use Friendica\Test\Util\VFSTrait; -class BaseURLTest extends MockedTest +class BaseURLTest extends DatabaseTest { use VFSTrait; + use CreateDatabaseTrait; protected function setUp(): void { @@ -302,8 +305,7 @@ class BaseURLTest extends MockedTest */ public function testSave($input, $save, $url) { - $configFileManager = new ConfigFileManager($this->root->url(), $this->root->url() . '/config/', $this->root->url() . '/static/'); - $config = new Config($configFileManager, new Cache([ + $config = new DatabaseConfig($this->getDbInstance(), new Cache([ 'config' => [ 'hostname' => $input['hostname'] ?? null, ], @@ -332,8 +334,7 @@ class BaseURLTest extends MockedTest */ public function testSaveByUrl($input, $save, $url) { - $configFileManager = new ConfigFileManager($this->root->url(), $this->root->url() . '/config/', $this->root->url() . '/static/'); - $config = new Config($configFileManager, new Cache([ + $config = new DatabaseConfig($this->getDbInstance(), new Cache([ 'config' => [ 'hostname' => $input['hostname'] ?? null, ], diff --git a/update.php b/update.php index 7d9a9d236..86735f864 100644 --- a/update.php +++ b/update.php @@ -1253,3 +1253,37 @@ function update_1513() DI::config()->delete('system', 'git_friendica_version'); DI::config()->delete('twitter', 'application_name'); } + +function update_1514() +{ + if (file_exists(dirname(__FILE__) . '/config/node.config.php')) { + + $transactionalConfig = DI::config()->beginTransaction(); + $oldConfig = include dirname(__FILE__) . '/config/node.config.php'; + + if (is_array($oldConfig)) { + $categories = array_keys($oldConfig); + + foreach ($categories as $category) { + if (is_array($oldConfig[$category])) { + $keys = array_keys($oldConfig[$category]); + + foreach ($keys as $key) { + $transactionalConfig->set($category, $key, $oldConfig[$category][$key]); + } + } + } + } + + $transactionalConfig->commit(); + + // Rename the node.config.php so it won't get used, but it isn't deleted. + if (rename(dirname(__FILE__) . '/config/node.config.php', dirname(__FILE__) . '/config/node.config.php.bak')) { + return Update::SUCCESS; + } else { + return Update::FAILED; + } + } + + return Update::SUCCESS; +} diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index e45042cba..89ebb0aa4 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.03-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-02-04 19:53-0500\n" +"POT-Creation-Date: 2023-02-12 12:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3188,7 +3188,7 @@ msgstr "" msgid "[no subject]" msgstr "" -#: src/Model/Photo.php:1184 src/Module/Media/Photo/Upload.php:198 +#: src/Model/Photo.php:1189 src/Module/Media/Photo/Upload.php:198 msgid "Wall Photos" msgstr "" @@ -3296,7 +3296,7 @@ msgstr "" msgid "Title/Description:" msgstr "" -#: src/Model/Profile.php:1023 src/Module/Admin/Summary.php:221 +#: src/Model/Profile.php:1023 src/Module/Admin/Summary.php:217 #: src/Module/Moderation/Summary.php:77 msgid "Summary" msgstr "" @@ -3623,7 +3623,7 @@ msgstr "" #: src/Module/Admin/Federation.php:207 src/Module/Admin/Logs/Settings.php:79 #: src/Module/Admin/Logs/View.php:84 src/Module/Admin/Queue.php:72 #: src/Module/Admin/Site.php:435 src/Module/Admin/Storage.php:138 -#: src/Module/Admin/Summary.php:220 src/Module/Admin/Themes/Details.php:90 +#: src/Module/Admin/Summary.php:216 src/Module/Admin/Themes/Details.php:90 #: src/Module/Admin/Themes/Index.php:111 src/Module/Admin/Tos.php:77 #: src/Module/Moderation/Users/Create.php:61 #: src/Module/Moderation/Users/Pending.php:96 @@ -5091,15 +5091,7 @@ msgid "" "with the transition." msgstr "" -#: src/Module/Admin/Summary.php:118 -#, php-format -msgid "" -"Friendica's configuration store \"%s\" isn't writable. Until then database " -"updates won't be applied automatically, admin settings and console " -"configuration changes won't be saved." -msgstr "" - -#: src/Module/Admin/Summary.php:124 +#: src/Module/Admin/Summary.php:120 #, php-format msgid "" "%s is not reachable on your system. This is a severe " @@ -5107,50 +5099,50 @@ msgid "" "href=\"%s\">the installation page for help." msgstr "" -#: src/Module/Admin/Summary.php:142 +#: src/Module/Admin/Summary.php:138 #, php-format msgid "The logfile '%s' is not usable. No logging possible (error: '%s')" msgstr "" -#: src/Module/Admin/Summary.php:156 +#: src/Module/Admin/Summary.php:152 #, php-format msgid "The debug logfile '%s' is not usable. No logging possible (error: '%s')" msgstr "" -#: src/Module/Admin/Summary.php:172 +#: src/Module/Admin/Summary.php:168 #, php-format msgid "" "Friendica's system.basepath was updated from '%s' to '%s'. Please remove the " "system.basepath from your db to avoid differences." msgstr "" -#: src/Module/Admin/Summary.php:180 +#: src/Module/Admin/Summary.php:176 #, php-format msgid "" "Friendica's current system.basepath '%s' is wrong and the config file '%s' " "isn't used." msgstr "" -#: src/Module/Admin/Summary.php:188 +#: src/Module/Admin/Summary.php:184 #, php-format msgid "" "Friendica's current system.basepath '%s' is not equal to the config file " "'%s'. Please fix your configuration." msgstr "" -#: src/Module/Admin/Summary.php:199 +#: src/Module/Admin/Summary.php:195 msgid "Message queues" msgstr "" -#: src/Module/Admin/Summary.php:205 +#: src/Module/Admin/Summary.php:201 msgid "Server Settings" msgstr "" -#: src/Module/Admin/Summary.php:223 +#: src/Module/Admin/Summary.php:219 msgid "Version" msgstr "" -#: src/Module/Admin/Summary.php:227 +#: src/Module/Admin/Summary.php:223 msgid "Active addons" msgstr ""