$this->tpl->assign('shaarlititle', $conf->get('title', 'Shaarli'));
$this->tpl->assign('openshaarli', $conf->get('config.OPEN_SHAARLI', false));
$this->tpl->assign('showatom', $conf->get('config.SHOW_ATOM', false));
+ $this->tpl->assign('hide_timestamps', $conf->get('config.HIDE_TIMESTAMPS', false));
// FIXME! Globals
if (!empty($GLOBALS['plugin_errors'])) {
$this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']);
$this->linkDB->savedb($conf->get('config.PAGECACHE'));
return true;
}
+
+ /**
+ * Move old configuration in PHP to the new config system in JSON format.
+ *
+ * Will rename 'config.php' into 'config.save.php' and create 'config.json'.
+ */
+ public function updateMethodConfigToJson()
+ {
+ $conf = ConfigManager::getInstance();
+
+ // JSON config already exists, nothing to do.
+ if ($conf->getConfigIO() instanceof ConfigJson) {
+ return true;
+ }
+
+ $configPhp = new ConfigPhp();
+ $configJson = new ConfigJson();
+ $oldConfig = $configPhp->read($conf::$CONFIG_FILE . '.php');
+ rename($conf->getConfigFile(), $conf::$CONFIG_FILE . '.save.php');
+ $conf->setConfigIO($configJson);
+ $conf->reload();
+
+ foreach (ConfigPhp::$ROOT_KEYS as $key) {
+ $conf->set($key, $oldConfig[$key]);
+ }
+
+ // Set sub config keys (config and plugins)
+ $subConfig = array('config', 'plugins');
+ foreach ($subConfig as $sub) {
+ foreach ($oldConfig[$sub] as $key => $value) {
+ $conf->set($sub .'.'. $key, $value);
+ }
+ }
+
+ try{
+ $conf->write($this->isLoggedIn);
+ return true;
+ } catch (IOException $e) {
+ error_log($e->getMessage());
+ return false;
+ }
+ }
}
/**
}
}
-
/**
* Read the updates file, and return already done updates.
*
*
* @param string $filepath Config file absolute path.
* @param array $conf All configuration in an array.
- *
- * @return bool True if the configuration has been successfully written, false otherwise.
*/
function write($filepath, $conf);
--- /dev/null
+<?php
+
+/**
+ * Class ConfigJson (ConfigIO implementation)
+ *
+ * Handle Shaarli's JSON configuration file.
+ */
+class ConfigJson implements ConfigIO
+{
+ /**
+ * The JSON data is wrapped in a PHP file for security purpose.
+ * This way, even if the file is accessible, credentials and configuration won't be exposed.
+ *
+ * @var string PHP start tag and comment tag.
+ */
+ public static $PHP_HEADER;
+
+ public function __construct()
+ {
+ // The field can't be initialized directly with concatenation before PHP 5.6.
+ self::$PHP_HEADER = '<?php /*'. PHP_EOL;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ function read($filepath)
+ {
+ if (! file_exists($filepath) || ! is_readable($filepath)) {
+ return array();
+ }
+ $data = file_get_contents($filepath);
+ $data = str_replace(self::$PHP_HEADER, '', $data);
+ $data = json_decode($data, true);
+ if ($data === null) {
+ $error = json_last_error();
+ throw new Exception('An error occured while parsing JSON file: error code #'. $error);
+ }
+ return $data;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ function write($filepath, $conf)
+ {
+ // JSON_PRETTY_PRINT is available from PHP 5.4.
+ $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
+ $data = self::$PHP_HEADER . json_encode($conf, $print);
+ if (!file_put_contents($filepath, $data)) {
+ throw new IOException(
+ $filepath,
+ 'Shaarli could not create the config file.
+ Please make sure Shaarli has the right to write in the folder is it installed in.'
+ );
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ function getExtension()
+ {
+ return '.json.php';
+ }
+}
// FIXME! Namespaces...
require_once 'ConfigIO.php';
require_once 'ConfigPhp.php';
-#require_once 'ConfigJson.php';
+require_once 'ConfigJson.php';
/**
* Class ConfigManager
*/
protected function initialize()
{
- /*if (! file_exists(self::$CONFIG_FILE .'.php')) {
+ if (! file_exists(self::$CONFIG_FILE .'.php')) {
$this->configIO = new ConfigJson();
} else {
$this->configIO = new ConfigPhp();
- }*/
- $this->configIO = new ConfigPhp();
+ }
$this->load();
}
protected static $testDatastore = 'sandbox/datastore.php';
/**
- * @var string Config file path.
+ * @var string Config file path (without extension).
*/
- protected static $configFile = 'tests/Updater/config.php';
+ protected static $configFile = 'tests/utils/config/configUpdater';
/**
* @var ConfigManager
)
);
- ConfigManager::$CONFIG_FILE = 'tests/Updater/config';
- $this->conf = ConfigManager::getInstance();
+ ConfigManager::$CONFIG_FILE = self::$configFile;
+ $this->conf = ConfigManager::reset();
+ $this->conf->reload();
foreach (self::$configFields as $key => $value) {
$this->conf->set($key, $value);
}
*/
public function tearDown()
{
- if (is_file(self::$configFile)) {
- unlink(self::$configFile);
+ if (is_file('tests/Updater/config.json')) {
+ unlink('tests/Updater/config.json');
}
if (is_file(self::$configFields['config']['DATADIR'] . '/options.php')) {
{
// Use writeConfig to create a options.php
ConfigManager::$CONFIG_FILE = 'tests/Updater/options';
+ $this->conf->setConfigIO(new ConfigPhp());
+
$invert = !$this->conf->get('privateLinkByDefault');
$this->conf->set('privateLinkByDefault', $invert);
$this->conf->write(true);
// merge configs
$updater = new Updater(array(), array(), true);
+ // This writes a new config file in tests/Updater/config.php
$updater->updateMethodMergeDeprecatedConfigFile();
// make sure updated field is changed
$this->conf->reload();
$this->assertEquals($invert, $this->conf->get('privateLinkByDefault'));
$this->assertFalse(is_file($optionsFile));
+ // Delete the generated file.
+ unlink($this->conf->getConfigFile());
}
/**
$updater->updateMethodRenameDashTags();
$this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
}
+
+ /**
+ * Convert old PHP config file to JSON config.
+ */
+ public function testConfigToJson()
+ {
+ $configFile = 'tests/utils/config/configPhp';
+ ConfigManager::$CONFIG_FILE = $configFile;
+ $conf = ConfigManager::reset();
+
+ // The ConfigIO is initialized with ConfigPhp.
+ $this->assertTrue($conf->getConfigIO() instanceof ConfigPhp);
+
+ $updater = new Updater(array(), array(), false);
+ $done = $updater->updateMethodConfigToJson();
+ $this->assertTrue($done);
+
+ // The ConfigIO has been updated to ConfigJson.
+ $this->assertTrue($conf->getConfigIO() instanceof ConfigJson);
+ $this->assertTrue(file_exists($conf->getConfigFile()));
+
+ // Check JSON config data.
+ $conf->reload();
+ $this->assertEquals('root', $conf->get('login'));
+ $this->assertEquals('lala', $conf->get('redirector'));
+ $this->assertEquals('data/datastore.php', $conf->get('config.DATASTORE'));
+ $this->assertEquals('1', $conf->get('plugins.WALLABAG_VERSION'));
+
+ rename($configFile . '.save.php', $configFile . '.php');
+ unlink($conf->getConfigFile());
+ }
+
+ /**
+ * Launch config conversion update with an existing JSON file => nothing to do.
+ */
+ public function testConfigToJsonNothingToDo()
+ {
+ $configFile = 'tests/utils/config/configUpdateDone';
+ ConfigManager::$CONFIG_FILE = $configFile;
+ $conf = ConfigManager::reset();
+ $conf->reload();
+ $filetime = filemtime($conf->getConfigFile());
+ $updater = new Updater(array(), array(), false);
+ $done = $updater->updateMethodConfigToJson();
+ $this->assertTrue($done);
+ $expected = filemtime($conf->getConfigFile());
+ $this->assertEquals($expected, $filetime);
+ }
}
--- /dev/null
+<?php
+
+require_once 'application/config/ConfigJson.php';
+
+/**
+ * Class ConfigJsonTest
+ */
+class ConfigJsonTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @var ConfigJson
+ */
+ protected $configIO;
+
+ public function setUp()
+ {
+ $this->configIO = new ConfigJson();
+ }
+
+ /**
+ * Read a simple existing config file.
+ */
+ public function testRead()
+ {
+ $conf = $this->configIO->read('tests/utils/config/configJson.json.php');
+ $this->assertEquals('root', $conf['login']);
+ $this->assertEquals('lala', $conf['redirector']);
+ $this->assertEquals('data/datastore.php', $conf['config']['DATASTORE']);
+ $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
+ }
+
+ /**
+ * Read a non existent config file -> empty array.
+ */
+ public function testReadNonExistent()
+ {
+ $this->assertEquals(array(), $this->configIO->read('nope'));
+ }
+
+ /**
+ * Read a non existent config file -> empty array.
+ *
+ * @expectedException Exception
+ * @expectedExceptionMessage An error occured while parsing JSON file: error code #4
+ */
+ public function testReadInvalidJson()
+ {
+ $this->configIO->read('tests/utils/config/configInvalid.json.php');
+ }
+
+ /**
+ * Write a new config file.
+ */
+ public function testWriteNew()
+ {
+ $dataFile = 'tests/utils/config/configWrite.json.php';
+ $data = array(
+ 'login' => 'root',
+ 'redirector' => 'lala',
+ 'config' => array(
+ 'DATASTORE' => 'data/datastore.php',
+ ),
+ 'plugins' => array(
+ 'WALLABAG_VERSION' => '1',
+ )
+ );
+ $this->configIO->write($dataFile, $data);
+ // PHP 5.3 doesn't support json pretty print.
+ if (defined('JSON_PRETTY_PRINT')) {
+ $expected = '{
+ "login": "root",
+ "redirector": "lala",
+ "config": {
+ "DATASTORE": "data\/datastore.php"
+ },
+ "plugins": {
+ "WALLABAG_VERSION": "1"
+ }
+}';
+ } else {
+ $expected = '{"login":"root","redirector":"lala","config":{"DATASTORE":"data\/datastore.php"},"plugins":{"WALLABAG_VERSION":"1"}}';
+ }
+ $expected = ConfigJson::$PHP_HEADER . $expected;
+ $this->assertEquals($expected, file_get_contents($dataFile));
+ unlink($dataFile);
+ }
+
+ /**
+ * Overwrite an existing setting.
+ */
+ public function testOverwrite()
+ {
+ $source = 'tests/utils/config/configJson.json.php';
+ $dest = 'tests/utils/config/configOverwrite.json.php';
+ copy($source, $dest);
+ $conf = $this->configIO->read($dest);
+ $conf['redirector'] = 'blabla';
+ $this->configIO->write($dest, $conf);
+ $conf = $this->configIO->read($dest);
+ $this->assertEquals('blabla', $conf['redirector']);
+ unlink($dest);
+ }
+
+ /**
+ * Write to invalid path.
+ *
+ * @expectedException IOException
+ */
+ public function testWriteInvalidArray()
+ {
+ $conf = array('conf' => 'value');
+ @$this->configIO->write(array(), $conf);
+ }
+
+ /**
+ * Write to invalid path.
+ *
+ * @expectedException IOException
+ */
+ public function testWriteInvalidBlank()
+ {
+ $conf = array('conf' => 'value');
+ @$this->configIO->write('', $conf);
+ }
+}
* Note: it only test the manager with ConfigJson,
* ConfigPhp is only a workaround to handle the transition to JSON type.
*/
-class ConfigManagerTest extends \PHPUnit_Framework_TestCase
+class ConfigManagerTest extends PHPUnit_Framework_TestCase
{
/**
* @var ConfigManager
public function setUp()
{
- ConfigManager::$CONFIG_FILE = 'tests/config/config';
- $this->conf = ConfigManager::getInstance();
+ ConfigManager::$CONFIG_FILE = 'tests/utils/config/configJson';
+ $this->conf = ConfigManager::reset();
}
- public function tearDown()
+ /**
+ * Simple config test:
+ * 1. Set settings.
+ * 2. Check settings value.
+ */
+ public function testSetGet()
{
- @unlink($this->conf->getConfigFile());
+ $this->conf->set('paramInt', 42);
+ $this->conf->set('paramString', 'value1');
+ $this->conf->set('paramBool', false);
+ $this->conf->set('paramArray', array('foo' => 'bar'));
+ $this->conf->set('paramNull', null);
+
+ $this->assertEquals(42, $this->conf->get('paramInt'));
+ $this->assertEquals('value1', $this->conf->get('paramString'));
+ $this->assertFalse($this->conf->get('paramBool'));
+ $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
+ $this->assertEquals(null, $this->conf->get('paramNull'));
}
+ /**
+ * Set/write/get config test:
+ * 1. Set settings.
+ * 2. Write it to the config file.
+ * 3. Read the file.
+ * 4. Check settings value.
+ */
public function testSetWriteGet()
{
- // This won't work with ConfigPhp.
- $this->markTestIncomplete();
-
$this->conf->set('paramInt', 42);
$this->conf->set('paramString', 'value1');
$this->conf->set('paramBool', false);
$this->conf->set('paramArray', array('foo' => 'bar'));
$this->conf->set('paramNull', null);
+ ConfigManager::$CONFIG_FILE = 'tests/utils/config/configTmp';
$this->conf->write(true);
$this->conf->reload();
+ unlink($this->conf->getConfigFile());
$this->assertEquals(42, $this->conf->get('paramInt'));
$this->assertEquals('value1', $this->conf->get('paramString'));
$this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
$this->assertEquals(null, $this->conf->get('paramNull'));
}
-
-}
\ No newline at end of file
+
+ /**
+ * Test set/write/get with nested keys.
+ */
+ public function testSetWriteGetNested()
+ {
+ $this->conf->set('foo.bar.key.stuff', 'testSetWriteGetNested');
+
+ ConfigManager::$CONFIG_FILE = 'tests/utils/config/configTmp';
+ $this->conf->write(true);
+ $this->conf->reload();
+ unlink($this->conf->getConfigFile());
+
+ $this->assertEquals('testSetWriteGetNested', $this->conf->get('foo.bar.key.stuff'));
+ }
+
+ /**
+ * Set with an empty key.
+ *
+ * @expectedException Exception
+ * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
+ */
+ public function testSetEmptyKey()
+ {
+ $this->conf->set('', 'stuff');
+ }
+
+ /**
+ * Set with an array key.
+ *
+ * @expectedException Exception
+ * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
+ */
+ public function testSetArrayKey()
+ {
+ $this->conf->set(array('foo' => 'bar'), 'stuff');
+ }
+
+ /**
+ * Try to write the config without mandatory parameter (e.g. 'login').
+ *
+ * @expectedException MissingFieldConfigException
+ */
+ public function testWriteMissingParameter()
+ {
+ ConfigManager::$CONFIG_FILE = 'tests/utils/config/configTmp';
+ $this->assertFalse(file_exists($this->conf->getConfigFile()));
+ $this->conf->reload();
+
+ $this->conf->write(true);
+ }
+
+ /**
+ * Try to get non existent config keys.
+ */
+ public function testGetNonExistent()
+ {
+ $this->assertEquals('', $this->conf->get('nope.test'));
+ $this->assertEquals('default', $this->conf->get('nope.test', 'default'));
+ }
+
+ /**
+ * Test the 'exists' method with existent values.
+ */
+ public function testExistsOk()
+ {
+ $this->assertTrue($this->conf->exists('login'));
+ $this->assertTrue($this->conf->exists('config.foo'));
+ }
+
+ /**
+ * Test the 'exists' method with non existent or invalid values.
+ */
+ public function testExistsKo()
+ {
+ $this->assertFalse($this->conf->exists('nope'));
+ $this->assertFalse($this->conf->exists('nope.nope'));
+ $this->assertFalse($this->conf->exists(''));
+ $this->assertFalse($this->conf->exists(false));
+ }
+
+ /**
+ * Reset the ConfigManager instance.
+ */
+ public function testReset()
+ {
+ $conf = $this->conf;
+ $this->assertTrue($conf === ConfigManager::getInstance());
+ $this->assertFalse($conf === $this->conf->reset());
+ $this->assertFalse($conf === ConfigManager::getInstance());
+ }
+
+ /**
+ * Reload the config from file.
+ */
+ public function testReload()
+ {
+ ConfigManager::$CONFIG_FILE = 'tests/utils/config/configTmp';
+ $newConf = ConfigJson::$PHP_HEADER . '{ "key": "value" }';
+ file_put_contents($this->conf->getConfigFile(), $newConf);
+ $this->conf->reload();
+ unlink($this->conf->getConfigFile());
+ // Previous conf no longer exists, and new values have been loaded.
+ $this->assertFalse($this->conf->exists('login'));
+ $this->assertEquals('value', $this->conf->get('key'));
+ }
+}
--- /dev/null
+<?php /*
+{
+ bad: bad,
+}
--- /dev/null
+<?php /*
+{
+ "redirector":"lala",
+ "login":"root",
+ "hash":"hash",
+ "salt":"salt",
+ "timezone":"Europe\/Paris",
+ "disablesessionprotection":false,
+ "privateLinkByDefault":true,
+ "title": "Shaarli",
+ "titleLink": "?",
+ "config": {
+ "foo": "bar",
+ "DATASTORE": "data\/datastore.php"
+ },
+ "plugins": {
+ "WALLABAG_VERSION": 1
+ }
+}
--- /dev/null
+<?php /*
+{
+ "login": "root"
+}
</span>
<br>
{if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
- {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"}
+ {if="!$hide_timestamps || isLoggedIn()"}
<span class="linkdate" title="Permalink"><a href="?{$value.linkdate|smallHash}">{function="strftime('%c', $value.timestamp)"} - permalink</a> - </span>
{else}
<span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span>
<br><br>
<a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
<br><br>
- {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
+ {if="$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
<br><br>{/if}
<a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
<br><br>