\Friendica\DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
+
+// Check the database structure and possibly fixes it
+\Friendica\Core\Update::check(\Friendica\DI::basePath(), true, \Friendica\DI::mode());
+
$appMode = $dice->create(Mode::class);
if ($appMode->isNormal()) {
use Dice\Dice;
use Friendica\App\Mode;
use Friendica\Core\Logger;
+use Friendica\Core\Update;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
-$a = DI::app();
+
+// Check the database structure and possibly fixes it
+Update::check(DI::basePath(), true, DI::mode());
if (DI::mode()->isInstall()) {
die("Friendica isn't properly installed yet.\n");
DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
-$a = DI::app();
DI::mode()->setExecutor(Mode::WORKER);
// Check the database structure and possibly fixes it
-Update::check($a->getBasePath(), true, DI::mode());
+Update::check(DI::basePath(), true, DI::mode());
// Quit when in maintenance
if (!DI::mode()->has(App\Mode::MAINTENANCEDISABLED)) {
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
+// Check the database structure and possibly fixes it
+\Friendica\Core\Update::check(\Friendica\DI::basePath(), true, \Friendica\DI::mode());
+
$a = \Friendica\DI::app();
\Friendica\DI::mode()->setExecutor(\Friendica\App\Mode::INDEX);
/** {@inheritDoc} */
public function delete(string $cat, string $key): bool
{
- if ($this->configCache->delete($cat, $key)) {
+ if ($this->configCache->delete($cat, $key, Cache::SOURCE_DATA)) {
$this->save();
return true;
} else {
protected $config;
/** @var Cache */
protected $cache;
- /** @var Cache */
- protected $delCache;
/** @var bool field to check if something is to save */
protected $changedConfig = false;
public function __construct(IManageConfigValues $config)
{
- $this->config = $config;
- $this->cache = new Cache();
- $this->delCache = new Cache();
- }
-
- /**
- * Get a particular user's config variable given the category name
- * ($cat) and a $key from the current transaction.
- *
- * Isn't part of the interface because of it's rare use case
- *
- * @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)
- {
- return !$this->delCache->get($cat, $key) ?
- ($this->cache->get($cat, $key) ?? $this->config->get($cat, $key)) :
- null;
+ $this->config = $config;
+ $this->cache = clone $config->getCache();
}
/** {@inheritDoc} */
/** {@inheritDoc} */
public function delete(string $cat, string $key): ISetConfigValuesTransactionally
{
- $this->cache->delete($cat, $key);
- $this->delCache->set($cat, $key, 'deleted');
+ $this->cache->delete($cat, $key, Cache::SOURCE_DATA);
$this->changedConfig = true;
return $this;
}
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();
+ $this->config->load($this->cache);
+ $this->cache = clone $this->config->getCache();
} catch (\Exception $e) {
throw new ConfigPersistenceException('Cannot save config', $e);
}
* First loads the default value for all the configuration keys, then the legacy configuration files, then the
* expected local.config.php
*
- * @param Cache $config The config cache to load to
- * @param bool $raw Set up the raw config format
+ * @param Cache $configCache The config cache to load to
+ * @param bool $raw Set up the raw config format
*
* @throws ConfigFileException
*/
- public function setupCache(Cache $config, bool $raw = false)
+ public function setupCache(Cache $configCache, bool $raw = false)
{
// Load static config files first, the order is important
- $config->load($this->loadStaticConfig('defaults'), Cache::SOURCE_STATIC);
- $config->load($this->loadStaticConfig('settings'), Cache::SOURCE_STATIC);
+ $configCache->load($this->loadStaticConfig('defaults'), Cache::SOURCE_STATIC);
+ $configCache->load($this->loadStaticConfig('settings'), Cache::SOURCE_STATIC);
// try to load the legacy config first
- $config->load($this->loadLegacyConfig('htpreconfig'), Cache::SOURCE_FILE);
- $config->load($this->loadLegacyConfig('htconfig'), Cache::SOURCE_FILE);
+ $configCache->load($this->loadLegacyConfig('htpreconfig'), Cache::SOURCE_FILE);
+ $configCache->load($this->loadLegacyConfig('htconfig'), Cache::SOURCE_FILE);
// Now load every other config you find inside the 'config/' directory
- $this->loadCoreConfig($config);
+ $this->loadCoreConfig($configCache);
// Now load the node.config.php file with the node specific config values (based on admin gui/console actions)
- $this->loadDataConfig($config);
+ $this->loadDataConfig($configCache);
- $config->load($this->loadEnvConfig(), Cache::SOURCE_ENV);
+ $configCache->load($this->loadEnvConfig(), Cache::SOURCE_ENV);
// In case of install mode, add the found basepath (because there isn't a basepath set yet
- if (!$raw && empty($config->get('system', 'basepath'))) {
+ if (!$raw && empty($configCache->get('system', 'basepath'))) {
// Setting at least the basepath we know
- $config->set('system', 'basepath', $this->baseDir, Cache::SOURCE_FILE);
+ $configCache->set('system', 'basepath', $this->baseDir, Cache::SOURCE_FILE);
}
}
if (file_exists($configName)) {
return $this->loadConfigFile($configName);
- } elseif (file_exists($iniName)) {
+ } else if (file_exists($iniName)) {
return $this->loadINIConfigFile($iniName);
} else {
return [];
/**
* Tries to load the specified core-configuration into the config cache.
*
- * @param Cache $config The Config cache
+ * @param Cache $configCache The Config cache
*
* @throws ConfigFileException if the configuration file isn't readable
*/
- private function loadCoreConfig(Cache $config)
+ private function loadCoreConfig(Cache $configCache)
{
// try to load legacy ini-files first
foreach ($this->getConfigFiles(true) as $configFile) {
- $config->load($this->loadINIConfigFile($configFile), Cache::SOURCE_FILE);
+ $configCache->load($this->loadINIConfigFile($configFile), Cache::SOURCE_FILE);
}
// try to load supported config at last to overwrite it
foreach ($this->getConfigFiles() as $configFile) {
- $config->load($this->loadConfigFile($configFile), Cache::SOURCE_FILE);
+ $configCache->load($this->loadConfigFile($configFile), Cache::SOURCE_FILE);
}
}
/**
* Tries to load the data config file with the overridden data
*
- * @param Cache $config The Config cache
+ * @param Cache $configCache The Config cache
*
* @throws ConfigFileException In case the config file isn't loadable
*/
- private function loadDataConfig(Cache $config)
+ private function loadDataConfig(Cache $configCache)
{
$filename = $this->configDir . '/' . self::CONFIG_DATA_FILE;
$dataArray = eval('?>' . $content);
- if (!is_array($dataArray)) {
- throw new ConfigFileException(sprintf('Error loading config file %s', $filename));
+ if (is_array($dataArray)) {
+ $configCache->load($dataArray, Cache::SOURCE_DATA);
}
-
- $config->load($dataArray, Cache::SOURCE_DATA);
}
}
/**
* Saves overridden config entries back into the data.config.phpR
*
- * @param Cache $config The config cache
+ * @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 $config)
+ public function saveData(Cache $configCache)
{
- $data = $config->getDataBySource(Cache::SOURCE_DATA);
-
- $encodedData = ConfigFileTransformer::encode($data);
+ $filename = $this->configDir . '/' . self::CONFIG_DATA_FILE;
- if (!$encodedData) {
- throw new ConfigFileException('config source cannot get encoded');
+ if (file_exists($filename)) {
+ $fileExists = true;
+ } else {
+ $fileExists = false;
}
- $configStream = fopen($this->configDir . '/' . self::CONFIG_DATA_FILE, 'w');
+ $configStream = fopen($filename, 'c+');
- if (flock($configStream, LOCK_EX)) {
- fwrite($configStream, $encodedData);
- fflush($configStream);
- flock($configStream, LOCK_UN);
+ try {
+ if (flock($configStream, LOCK_EX)) {
+
+ if ($fileExists) {
+ clearstatcache(true, $filename);
+ $content = fread($configStream, filesize($filename));
+ rewind($configStream);
+ if (!$content) {
+ throw new ConfigFileException(sprintf('Couldn\'t read file %s', $filename));
+ }
+
+ $dataArray = eval('?>' . $content);
+
+ if (is_array($dataArray)) {
+ $fileConfigCache = new Cache();
+ $fileConfigCache->load($dataArray, Cache::SOURCE_DATA);
+ $configCache = $fileConfigCache->merge($configCache);
+ }
+ }
+
+ $data = $configCache->getDataBySource(Cache::SOURCE_DATA);
+
+ $encodedData = ConfigFileTransformer::encode($data);
+
+ if (!$encodedData) {
+ throw new ConfigFileException('config source cannot get encoded');
+ }
+
+ clearstatcache(true, $filename);
+ if (!ftruncate($configStream, 0) ||
+ !fwrite($configStream, $encodedData) ||
+ !fflush($configStream) ||
+ !flock($configStream, LOCK_UN)) {
+ throw new ConfigFileException(sprintf('Cannot modify locked file %s', $filename));
+ }
+ }
+ } finally {
+ fclose($configStream);
}
}
$filepath = $this->baseDir . DIRECTORY_SEPARATOR . // /var/www/html/
Addon::DIRECTORY . DIRECTORY_SEPARATOR . // addon/
$name . DIRECTORY_SEPARATOR . // openstreetmap/
- 'config'. DIRECTORY_SEPARATOR . // config/
+ 'config' . DIRECTORY_SEPARATOR . // config/
$name . ".config.php"; // openstreetmap.config.php
if (file_exists($filepath)) {
$categories = array_keys($data);
foreach ($categories as $category) {
+
+ if (is_null($data[$category])) {
+ $dataString .= "\t'$category' => null," . PHP_EOL;
+ continue;
+ }
+
$dataString .= "\t'$category' => [" . PHP_EOL;
if (is_array($data[$category])) {
{
$string = str_repeat("\t", $level + 2) . "'$key' => ";
- if (is_array($value)) {
+ if (is_null($value)) {
+ $string .= "null,";
+ } elseif (is_array($value)) {
$string .= "[" . PHP_EOL;
$string .= static::extractArray($value, ++$level);
$string .= str_repeat("\t", $level + 1) . '],';
* @param bool $hidePasswordOutput True, if cache variables should take extra care of password values
* @param int $source Sets a source of the initial config values
*/
- public function __construct(array $config = [], bool $hidePasswordOutput = true, $source = self::SOURCE_DEFAULT)
+ public function __construct(array $config = [], bool $hidePasswordOutput = true, int $source = self::SOURCE_DEFAULT)
{
$this->hidePasswordOutput = $hidePasswordOutput;
$this->load($config, $source);
$keys = array_keys($config[$category]);
foreach ($keys as $key) {
- $value = $config[$category][$key];
- if (isset($value)) {
- $this->set($category, $key, $value, $source);
- }
+ $this->set($category, $key, $config[$category][$key] ?? null, $source);
}
+ } else {
+ $this->set($category, null, $config[$category], $source);
}
}
}
$data[$category][$key] = $this->config[$category][$key];
}
}
+ } elseif (is_int($this->source[$category])) {
+ $data[$category] = null;
}
}
/**
* Sets a value in the config cache. Accepts raw output from the config table
*
- * @param string $cat Config category
- * @param string $key Config key
- * @param mixed $value Value to set
- * @param int $source The source of the current config key
+ * @param string $cat Config category
+ * @param ?string $key Config key
+ * @param ?mixed $value Value to set
+ * @param int $source The source of the current config key
*
* @return bool True, if the value is set
*/
- public function set(string $cat, string $key, $value, int $source = self::SOURCE_DEFAULT): bool
+ public function set(string $cat, string $key = null, $value = null, int $source = self::SOURCE_DEFAULT): bool
{
- if (!isset($this->config[$cat])) {
+ if (!isset($this->config[$cat]) && $key !== null) {
$this->config[$cat] = [];
$this->source[$cat] = [];
}
- if (isset($this->source[$cat][$key]) &&
- $source < $this->source[$cat][$key]) {
+ if ((isset($this->source[$cat][$key]) && $source < $this->source[$cat][$key]) ||
+ (is_int($this->source[$cat] ?? null) && $source < $this->source[$cat])) {
return false;
}
if ($this->hidePasswordOutput &&
$key == 'password' &&
is_string($value)) {
- $this->config[$cat][$key] = new HiddenString((string)$value);
+ $this->setCatKeyValueSource($cat, $key, new HiddenString((string)$value), $source);
} else if (is_string($value)) {
- $this->config[$cat][$key] = self::toConfigValue($value);
+ $this->setCatKeyValueSource($cat, $key, self::toConfigValue($value), $source);
} else {
- $this->config[$cat][$key] = $value;
+ $this->setCatKeyValueSource($cat, $key, $value, $source);
}
- $this->source[$cat][$key] = $source;
-
return true;
}
+ private function setCatKeyValueSource(string $cat, string $key = null, $value = null, int $source = self::SOURCE_DEFAULT)
+ {
+ if (isset($key)) {
+ $this->config[$cat][$key] = $value;
+ $this->source[$cat][$key] = $source;
+ } else {
+ $this->config[$cat] = $value;
+ $this->source[$cat] = $source;
+ }
+ }
+
/**
* Formats a DB value to a config value
* - null = The db-value isn't set
/**
* Deletes a value from the config cache.
*
- * @param string $cat Config category
- * @param string $key Config key
+ * @param string $cat Config category
+ * @param ?string $key Config key (if not set, the whole category will get deleted)
+ * @param int $source The source of the current config key
*
* @return bool true, if deleted
*/
- public function delete(string $cat, string $key): bool
+ public function delete(string $cat, string $key = null, int $source = self::SOURCE_DEFAULT): bool
{
if (isset($this->config[$cat][$key])) {
- unset($this->config[$cat][$key]);
- unset($this->source[$cat][$key]);
- if (count($this->config[$cat]) == 0) {
- unset($this->config[$cat]);
- unset($this->source[$cat]);
+ $this->config[$cat][$key] = null;
+ $this->source[$cat][$key] = $source;
+ if (empty(array_filter($this->config[$cat], function($value) { return !is_null($value); }))) {
+ $this->config[$cat] = null;
+ $this->source[$cat] = $source;
}
- return true;
- } else {
- return false;
+ } elseif (isset($this->config[$cat])) {
+ $this->config[$cat] = null;
+ $this->source[$cat] = $source;
}
+
+ return true;
}
/**
$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) {
- unset($newConfig[$category][$key]);
- unset($newSource[$category][$key]);
- }
+ } else {
+ $newConfig[$category] = $cache->config[$category];
+ $newSource[$category] = $cache->source[$category];
}
}
$configCache = new Cache();
$configCache->load($data);
+ print_r($configCache);
+
self::assertConfigValues($data, $configCache);
}
}
/**
- * Test the loadConfigArray() method with wrong/empty datasets
+ * Test the loadConfigArray() method with only a category
*/
- public function testLoadConfigArrayWrong()
+ public function testLoadConfigArrayWithOnlyCategory()
{
$configCache = new Cache();
// wrong dataset
$configCache->load(['system' => 'not_array']);
- self::assertEmpty($configCache->getAll());
+ self::assertEquals(['system' => 'not_array'], $configCache->getAll());
// incomplete dataset (key is integer ID of the array)
+ $configCache = new Cache();
$configCache->load(['system' => ['value']]);
self::assertEquals('value', $configCache->get('system', 0));
}
{
$configCache = new Cache($data);
+ $assertion = [];
+
foreach ($data as $cat => $values) {
+ $assertion[$cat] = null;
foreach ($values as $key => $value) {
$configCache->delete($cat, $key);
}
}
- self::assertEmpty($configCache->getAll());
+ self::assertEquals($assertion, $configCache->getAll());
}
/**
self::assertEquals('added category', $mergedCache->get('new_category', 'test_23'));
}
- /**
- * @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'));
- }
-
public function dataTestCat()
{
return [
'special' => $specialChars,
]], $configCache2->getDataBySource(Cache::SOURCE_DATA));
}
+
+ /**
+ * If we delete something with the Cache::delete() functionality, be sure to override the underlying source as well
+ */
+ 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', Cache::SOURCE_DATA);
+
+ $configFileManager->saveData($configCache);
+
+ // assert that system.default_timezone is now null, even it's set with settings.conf.php
+ $configCache = new Cache();
+
+ $configFileManager->setupCache($configCache);
+
+ self::assertNull($configCache->get('system', 'default_timezone'));
+ }
+
+ /**
+ * If we delete something with the Cache::delete() functionality, be sure to override the underlying source as well
+ */
+ public function testDeleteCategoryOverwrite()
+ {
+ $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');
+
+ $configFileManager->saveData($configCache);
+
+ // assert that system.default_timezone is now null, even it's set with settings.conf.php
+ $configCache = new Cache();
+
+ $configFileManager->setupCache($configCache);
+
+ self::assertNull($configCache->get('system', 'default_timezone'));
+ }
}
self::assertNull($this->testedConfig->get('test', 'it'));
self::assertNull($this->testedConfig->getCache()->get('test', 'it'));
- self::assertEmpty($this->testedConfig->getCache()->getAll());
+ self::assertEquals(['test' => null], $this->testedConfig->getCache()->getAll());
}
/**
$config->set('delete', 'keyDel', 'catDel');
$configTransaction = new ConfigTransaction($config);
- self::assertEquals('value1', $configTransaction->get('config', 'key1'));
- self::assertEquals('value2', $configTransaction->get('system', 'key2'));
- self::assertEquals('valueDel', $configTransaction->get('system', 'keyDel'));
- self::assertEquals('catDel', $configTransaction->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('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', $configTransaction->get('transaction', 'key3'));
- self::assertEquals('changedValue1', $configTransaction->get('config', 'key1'));
- self::assertNull($configTransaction->get('system', 'keyDel'));
- self::assertNull($configTransaction->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('value3', $config->get('transaction', 'key3'));
self::assertNull($config->get('system', 'keyDel'));
self::assertNull($config->get('delete', 'keyDel'));
- self::assertEquals('value3', $configTransaction->get('transaction', 'key3'));
- self::assertEquals('changedValue1', $configTransaction->get('config', 'key1'));
- self::assertNull($configTransaction->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);