Isolate functions related to config in Config.php + add unit tests + code_sniffer.
options.php is not supported anymore, but its content will be automatically saved into config.php
Fixes #shaarli/Shaarli#41
*TODO*: update [documentation](https://github.com/shaarli/Shaarli/wiki#configuration).
--- /dev/null
+<?php\r
+/**\r
+ * Functions related to configuration management.\r
+ */\r
+\r
+/**\r
+ * Re-write configuration file according to given array.\r
+ * Requires mandatory fields listed in $MANDATORY_FIELDS.\r
+ *\r
+ * @param array $config contains all configuration fields.\r
+ * @param bool $isLoggedIn true if user is logged in.\r
+ *\r
+ * @return void\r
+ *\r
+ * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.\r
+ * @throws UnauthorizedConfigException: user is not authorize to change configuration.\r
+ * @throws Exception: an error occured while writing the new config file.\r
+ */\r
+function writeConfig($config, $isLoggedIn)\r
+{\r
+ // These fields are required in configuration.\r
+ $MANDATORY_FIELDS = [\r
+ 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',\r
+ 'redirector', 'disablesessionprotection', 'privateLinkByDefault'\r
+ ];\r
+\r
+ if (!isset($config['config']['CONFIG_FILE'])) {\r
+ throw new MissingFieldConfigException('CONFIG_FILE');\r
+ }\r
+\r
+ // Only logged in user can alter config.\r
+ if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {\r
+ throw new UnauthorizedConfigException();\r
+ }\r
+\r
+ // Check that all mandatory fields are provided in $config.\r
+ foreach ($MANDATORY_FIELDS as $field) {\r
+ if (!isset($config[$field])) {\r
+ throw new MissingFieldConfigException($field);\r
+ }\r
+ }\r
+\r
+ $configStr = '<?php '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;\r
+ $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;\r
+\r
+ // Store all $config['config']\r
+ foreach ($config['config'] as $key => $value) {\r
+ $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;\r
+ }\r
+ $configStr .= '?>';\r
+\r
+ if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)\r
+ || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0\r
+ ) {\r
+ throw new Exception(\r
+ 'Shaarli could not create the config file.\r
+ Please make sure Shaarli has the right to write in the folder is it installed in.'\r
+ );\r
+ }\r
+}\r
+\r
+/**\r
+ * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.\r
+ * ==> if user is loggedIn, merge its content with config.php, then delete options.php.\r
+ *\r
+ * @param array $config contains all configuration fields.\r
+ * @param bool $isLoggedIn true if user is logged in.\r
+ *\r
+ * @return void\r
+ */\r
+function mergeDeprecatedConfig($config, $isLoggedIn)\r
+{\r
+ $config_file = $config['config']['CONFIG_FILE'];\r
+\r
+ if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {\r
+ include $config['config']['DATADIR'].'/options.php';\r
+\r
+ // Load GLOBALS into config\r
+ foreach ($GLOBALS as $key => $value) {\r
+ $config[$key] = $value;\r
+ }\r
+ $config['config']['CONFIG_FILE'] = $config_file;\r
+ writeConfig($config, $isLoggedIn);\r
+\r
+ unlink($config['config']['DATADIR'].'/options.php');\r
+ }\r
+}\r
+\r
+/**\r
+ * Exception used if a mandatory field is missing in given configuration.\r
+ */\r
+class MissingFieldConfigException extends Exception\r
+{\r
+ public $field;\r
+\r
+ /**\r
+ * Construct exception.\r
+ *\r
+ * @param string $field field name missing.\r
+ */\r
+ public function __construct($field)\r
+ {\r
+ $this->field = $field;\r
+ $this->message = 'Configuration value is required for '. $this->field;\r
+ }\r
+}\r
+\r
+/**\r
+ * Exception used if an unauthorized attempt to edit configuration has been made.\r
+ */\r
+class UnauthorizedConfigException extends Exception\r
+{\r
+ /**\r
+ * Construct exception.\r
+ */\r
+ public function __construct()\r
+ {\r
+ $this->message = 'You are not authorized to alter config.';\r
+ }\r
+}
\ No newline at end of file
date_default_timezone_set('UTC');
// -----------------------------------------------------------------------------------------------
-// Hardcoded parameter (These parameters can be overwritten by creating the file /data/options.php)
+// Hardcoded parameter (These parameters can be overwritten by editing the file /data/config.php)
+// You should not touch any code below (or at your own risks!)
$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory
$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password)
$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file.
$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true; // Enable RSS permalinks by default. This corresponds to the default behavior of shaarli before this was added as an option.
$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
// -----------------------------------------------------------------------------------------------
-// You should not touch below (or at your own risks!)
-// Optional config file.
-if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
-
define('shaarli_version','0.0.45beta');
// http://server.com/x/shaarli --> /shaarli/
define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
// Shaarli library
require_once 'application/LinkDB.php';
require_once 'application/Utils.php';
+require_once 'application/Config.php';
include "inc/rain.tpl.class.php"; //include Rain TPL
raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
-if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false;
if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
// I really need to rewrite Shaarli with a proper configuation manager.
if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
else if (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $linksToDisplay = $LINKSDB;
-
+
$nblinksToDisplay = 50; // Number of links to display.
if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
{
if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
else if (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $linksToDisplay = $LINKSDB;
-
+
$nblinksToDisplay = 50; // Number of links to display.
if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
{
if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']);
elseif (!empty($_GET['searchtags'])) $links = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $links = $LINKSDB;
-
+
$body='';
$linksToDisplay=array();
$linksToDisplay[]=$link; // Add to array.
}
}
-
+
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('linksToDisplay',$linksToDisplay);
// Save new password
$GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
$GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
- writeConfig();
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after changing password.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
+ exit;
+ }
echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
exit;
}
$GLOBALS['titleLink']=$_POST['titleLink'];
$GLOBALS['redirector']=$_POST['redirector'];
$GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
- $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
$GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
$GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
$GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']);
- writeConfig();
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after configuration update.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
+ exit;
+ }
echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
exit;
}
$GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
$GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
$GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
- writeConfig();
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after installation.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
+ exit;
+ }
echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
exit;
}
}
}
-// Re-write configuration file according to globals.
-// Requires some $GLOBALS to be set (login,hash,salt,title).
-// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
-// (otherwise, the function simply returns.)
-function writeConfig()
-{
- if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
- $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
- $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
- $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
- $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
- $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
- $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
- $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
- $config .= '$GLOBALS[\'config\'][\'ENABLE_RSS_PERMALINKS\']='.var_export($GLOBALS['config']['ENABLE_RSS_PERMALINKS'], true).'; ';
- $config .= '$GLOBALS[\'config\'][\'ENABLE_UPDATECHECK\']='.var_export($GLOBALS['config']['ENABLE_UPDATECHECK'], true).'; ';
- $config .= '$GLOBALS[\'config\'][\'HIDE_PUBLIC_LINKS\']='.var_export($GLOBALS['config']['HIDE_PUBLIC_LINKS'], true).'; ';
- $config .= ' ?>';
- if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
- {
- echo '<script>alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>';
- exit;
- }
-}
+
/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
pageCache::purgeCache(); // Purge page cache shared by sessions.
}
+try {
+ mergeDeprecatedConfig($GLOBALS, isLoggedIn());
+} catch(Exception $e) {
+ error_log(
+ 'ERROR while merging deprecated options.php file.' . PHP_EOL .
+ $e->getMessage()
+ );
+}
+
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
--- /dev/null
+<?php\r
+/**\r
+ * Config' tests\r
+ */\r
+\r
+require_once 'application/Config.php';\r
+\r
+/**\r
+ * Unitary tests for Shaarli config related functions\r
+ */\r
+class ConfigTest extends PHPUnit_Framework_TestCase\r
+{\r
+ // Configuration input set.\r
+ private static $_configFields;\r
+\r
+ /**\r
+ * Executed before each test.\r
+ */\r
+ public function setUp()\r
+ {\r
+ self::$_configFields = [\r
+ 'login' => 'login',\r
+ 'hash' => 'hash',\r
+ 'salt' => 'salt',\r
+ 'timezone' => 'Europe/Paris',\r
+ 'title' => 'title',\r
+ 'titleLink' => 'titleLink',\r
+ 'redirector' => '',\r
+ 'disablesessionprotection' => false,\r
+ 'privateLinkByDefault' => false,\r
+ 'config' => [\r
+ 'CONFIG_FILE' => 'tests/config.php',\r
+ 'DATADIR' => 'tests',\r
+ 'config1' => 'config1data',\r
+ 'config2' => 'config2data',\r
+ ]\r
+ ];\r
+ }\r
+\r
+ /**\r
+ * Executed after each test.\r
+ *\r
+ * @return void\r
+ */\r
+ public function tearDown()\r
+ {\r
+ if (is_file(self::$_configFields['config']['CONFIG_FILE'])) {\r
+ unlink(self::$_configFields['config']['CONFIG_FILE']);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function, valid use case, while being logged in.\r
+ */\r
+ public function testWriteConfig()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);\r
+ $this->assertEquals(self::$_configFields['hash'], $GLOBALS['hash']);\r
+ $this->assertEquals(self::$_configFields['salt'], $GLOBALS['salt']);\r
+ $this->assertEquals(self::$_configFields['timezone'], $GLOBALS['timezone']);\r
+ $this->assertEquals(self::$_configFields['title'], $GLOBALS['title']);\r
+ $this->assertEquals(self::$_configFields['titleLink'], $GLOBALS['titleLink']);\r
+ $this->assertEquals(self::$_configFields['redirector'], $GLOBALS['redirector']);\r
+ $this->assertEquals(self::$_configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);\r
+ $this->assertEquals(self::$_configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);\r
+ $this->assertEquals(self::$_configFields['config']['config1'], $GLOBALS['config']['config1']);\r
+ $this->assertEquals(self::$_configFields['config']['config2'], $GLOBALS['config']['config2']);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig option while logged in:\r
+ * 1. init fields.\r
+ * 2. update fields, add new sub config, add new root config.\r
+ * 3. rewrite config.\r
+ * 4. check result.\r
+ */\r
+ public function testWriteConfigFieldUpdate()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+ self::$_configFields['title'] = 'ok';\r
+ self::$_configFields['config']['config1'] = 'ok';\r
+ self::$_configFields['config']['config_new'] = 'ok';\r
+ self::$_configFields['new'] = 'should not be saved';\r
+ writeConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals('ok', $GLOBALS['title']);\r
+ $this->assertEquals('ok', $GLOBALS['config']['config1']);\r
+ $this->assertEquals('ok', $GLOBALS['config']['config_new']);\r
+ $this->assertFalse(isset($GLOBALS['new']));\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function with an empty array.\r
+ *\r
+ * @expectedException MissingFieldConfigException\r
+ */\r
+ public function testWriteConfigEmpty()\r
+ {\r
+ writeConfig(array(), true);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function with a missing mandatory field.\r
+ *\r
+ * @expectedException MissingFieldConfigException\r
+ */\r
+ public function testWriteConfigMissingField()\r
+ {\r
+ unset(self::$_configFields['login']);\r
+ writeConfig(self::$_configFields, true);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function while being logged out, and there is no config file existing.\r
+ */\r
+ public function testWriteConfigLoggedOutNoFile()\r
+ {\r
+ writeConfig(self::$_configFields, false);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function while being logged out, and a config file already exists.\r
+ *\r
+ * @expectedException UnauthorizedConfigException\r
+ */\r
+ public function testWriteConfigLoggedOutWithFile()\r
+ {\r
+ file_put_contents(self::$_configFields['config']['CONFIG_FILE'], '');\r
+ writeConfig(self::$_configFields, false);\r
+ }\r
+\r
+ /**\r
+ * Test mergeDeprecatedConfig while being logged in:\r
+ * 1. init a config file.\r
+ * 2. init a options.php file with update value.\r
+ * 3. merge.\r
+ * 4. check updated value in config file.\r
+ */\r
+ public function testMergeDeprecatedConfig()\r
+ {\r
+ // init\r
+ writeConfig(self::$_configFields, true);\r
+ $configCopy = self::$_configFields;\r
+ $invert = !$configCopy['privateLinkByDefault'];\r
+ $configCopy['privateLinkByDefault'] = $invert;\r
+\r
+ // Use writeConfig to create a options.php\r
+ $configCopy['config']['CONFIG_FILE'] = 'tests/options.php';\r
+ writeConfig($configCopy, true);\r
+\r
+ $this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));\r
+\r
+ // merge configs\r
+ mergeDeprecatedConfig(self::$_configFields, true);\r
+\r
+ // make sure updated field is changed\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);\r
+ $this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));\r
+ }\r
+\r
+ /**\r
+ * Test mergeDeprecatedConfig while being logged in without options file.\r
+ */\r
+ public function testMergeDeprecatedConfigNoFile()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+ mergeDeprecatedConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);\r
+ }\r
+}
\ No newline at end of file