]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Adds ConfigJson which handle the configuration in JSON format.
authorArthurHoaro <arthur@hoa.ro>
Sun, 29 May 2016 10:32:14 +0000 (12:32 +0200)
committerArthurHoaro <arthur@hoa.ro>
Sat, 11 Jun 2016 07:30:56 +0000 (09:30 +0200)
Also use the Updater to make the transition

13 files changed:
application/PageBuilder.php
application/Updater.php
application/config/ConfigIO.php
application/config/ConfigJson.php [new file with mode: 0644]
application/config/ConfigManager.php
tests/Updater/UpdaterTest.php
tests/config/ConfigJsonTest.php [new file with mode: 0644]
tests/config/ConfigManagerTest.php
tests/utils/config/configInvalid.json.php [new file with mode: 0644]
tests/utils/config/configJson.json.php [new file with mode: 0644]
tests/utils/config/configUpdateDone.json.php [new file with mode: 0644]
tpl/linklist.html
tpl/tools.html

index cf13c714631a47e8f71659061eb5c7338c99571c..1d3ba9e8641b8ae978bef499d68cf2edfb377fa3 100644 (file)
@@ -75,6 +75,7 @@ class PageBuilder
         $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']);
index 6b92af3d2d294164235f0feac77c53354051d8b6..8552850c276463318533d02d435bd322419ab80f 100644 (file)
@@ -142,6 +142,48 @@ class Updater
         $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;
+        }
+    }
 }
 
 /**
@@ -199,7 +241,6 @@ class UpdaterException extends Exception
     }
 }
 
-
 /**
  * Read the updates file, and return already done updates.
  *
index 4b1c990138e3c15a353822078e0968918a2ec16c..2b68fe6ab7b4af924371bf7b1403a0f39d2f784f 100644 (file)
@@ -21,8 +21,6 @@ interface ConfigIO
      *
      * @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);
 
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php
new file mode 100644 (file)
index 0000000..cbafbf6
--- /dev/null
@@ -0,0 +1,66 @@
+<?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';
+    }
+}
index 212aac050a26dde34ce18e069009d9d570e9534f..7045673755788fad17e0778625f69971f8847232 100644 (file)
@@ -3,7 +3,7 @@
 // FIXME! Namespaces...
 require_once 'ConfigIO.php';
 require_once 'ConfigPhp.php';
-#require_once 'ConfigJson.php';
+require_once 'ConfigJson.php';
 
 /**
  * Class ConfigManager
@@ -84,12 +84,11 @@ 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();
     }
 
index 8bfb4ba3bcc83fd179d79c97bdddc9c3c9cb864e..f8de2f706efb572d8c843c52ed8a8a060f1ec3f7 100644 (file)
@@ -20,9 +20,9 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
     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
@@ -52,8 +52,9 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
             )
         );
 
-        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);
         }
@@ -67,8 +68,8 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
      */
     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')) {
@@ -214,6 +215,8 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
     {
         // 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);
@@ -225,12 +228,15 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
 
         // 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());
     }
 
     /**
@@ -257,4 +263,52 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
         $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);
+    }
 }
diff --git a/tests/config/ConfigJsonTest.php b/tests/config/ConfigJsonTest.php
new file mode 100644 (file)
index 0000000..5b3bce4
--- /dev/null
@@ -0,0 +1,125 @@
+<?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);
+    }
+}
index 1b6358f307882b6465ccdeb71f62daafbd41b0a4..7390699ce80d5b6eb8546519f4d954da81377f02 100644 (file)
@@ -6,7 +6,7 @@
  * 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
@@ -15,28 +15,49 @@ class ConfigManagerTest extends \PHPUnit_Framework_TestCase
 
     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'));
@@ -44,5 +65,110 @@ class ConfigManagerTest extends \PHPUnit_Framework_TestCase
         $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'));
+    }
+}
diff --git a/tests/utils/config/configInvalid.json.php b/tests/utils/config/configInvalid.json.php
new file mode 100644 (file)
index 0000000..c53e471
--- /dev/null
@@ -0,0 +1,4 @@
+<?php /*
+{
+    bad: bad,
+}
diff --git a/tests/utils/config/configJson.json.php b/tests/utils/config/configJson.json.php
new file mode 100644 (file)
index 0000000..71b59ed
--- /dev/null
@@ -0,0 +1,19 @@
+<?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
+    }
+}
diff --git a/tests/utils/config/configUpdateDone.json.php b/tests/utils/config/configUpdateDone.json.php
new file mode 100644 (file)
index 0000000..a4e460d
--- /dev/null
@@ -0,0 +1,4 @@
+<?php /*
+{
+  "login": "root"
+}
index c0d420065562d7cb6b662cb6e0e03922bedd5f19..2316f145d94aeac694848ab0e5de6ae1f4c992e4 100644 (file)
@@ -88,7 +88,7 @@
                 </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>
index 78b816633ac5ca3b1723c3a6232b9236d7463b7b..29ade72596ddf363511876ed2e3724219398a88e 100644 (file)
@@ -9,7 +9,7 @@
                <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>