Merge pull request #570 from ArthurHoaro/config-manager

Introduce a configuration manager
This commit is contained in:
Arthur 2016-07-09 07:19:48 +02:00 committed by GitHub
commit 649af5b501
41 changed files with 2099 additions and 1133 deletions

View file

@ -132,11 +132,11 @@ public static function checkPHPVersion($minVersion, $curVersion)
/**
* Checks Shaarli has the proper access permissions to its resources
*
* @param array $globalConfig The $GLOBALS['config'] array
* @param ConfigManager $conf Configuration Manager instance.
*
* @return array A list of the detected configuration issues
*/
public static function checkResourcePermissions($globalConfig)
public static function checkResourcePermissions($conf)
{
$errors = array();
@ -145,7 +145,7 @@ public static function checkResourcePermissions($globalConfig)
'application',
'inc',
'plugins',
$globalConfig['RAINTPL_TPL']
$conf->get('resource.raintpl_tpl'),
) as $path) {
if (! is_readable(realpath($path))) {
$errors[] = '"'.$path.'" directory is not readable';
@ -154,10 +154,10 @@ public static function checkResourcePermissions($globalConfig)
// Check cache and data directories are readable and writeable
foreach (array(
$globalConfig['CACHEDIR'],
$globalConfig['DATADIR'],
$globalConfig['PAGECACHE'],
$globalConfig['RAINTPL_TMP']
$conf->get('resource.thumbnails_cache'),
$conf->get('resource.data_dir'),
$conf->get('resource.page_cache'),
$conf->get('resource.raintpl_tmp'),
) as $path) {
if (! is_readable(realpath($path))) {
$errors[] = '"'.$path.'" directory is not readable';
@ -169,11 +169,11 @@ public static function checkResourcePermissions($globalConfig)
// Check configuration files are readable and writeable
foreach (array(
$globalConfig['CONFIG_FILE'],
$globalConfig['DATASTORE'],
$globalConfig['IPBANS_FILENAME'],
$globalConfig['LOG_FILE'],
$globalConfig['UPDATECHECK_FILENAME']
$conf->getConfigFileExt(),
$conf->get('resource.datastore'),
$conf->get('resource.ban_file'),
$conf->get('resource.log'),
$conf->get('resource.update_check'),
) as $path) {
if (! is_file(realpath($path))) {
# the file may not exist yet

View file

@ -1,221 +0,0 @@
<?php
/**
* Functions related to configuration management.
*/
/**
* Re-write configuration file according to given array.
* Requires mandatory fields listed in $MANDATORY_FIELDS.
*
* @param array $config contains all configuration fields.
* @param bool $isLoggedIn true if user is logged in.
*
* @return void
*
* @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
* @throws UnauthorizedConfigException: user is not authorize to change configuration.
* @throws Exception: an error occured while writing the new config file.
*/
function writeConfig($config, $isLoggedIn)
{
// These fields are required in configuration.
$MANDATORY_FIELDS = array(
'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
'redirector', 'disablesessionprotection', 'privateLinkByDefault'
);
if (!isset($config['config']['CONFIG_FILE'])) {
throw new MissingFieldConfigException('CONFIG_FILE');
}
// Only logged in user can alter config.
if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
throw new UnauthorizedConfigException();
}
// Check that all mandatory fields are provided in $config.
foreach ($MANDATORY_FIELDS as $field) {
if (!isset($config[$field])) {
throw new MissingFieldConfigException($field);
}
}
$configStr = '<?php '. PHP_EOL;
$configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
$configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
$configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
// Store all $config['config']
foreach ($config['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
}
if (isset($config['plugins'])) {
foreach ($config['plugins'] as $key => $value) {
$configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
}
}
if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
|| strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
) {
throw new Exception(
'Shaarli could not create the config file.
Please make sure Shaarli has the right to write in the folder is it installed in.'
);
}
}
/**
* Process plugin administration form data and save it in an array.
*
* @param array $formData Data sent by the plugin admin form.
*
* @return array New list of enabled plugin, ordered.
*
* @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
*/
function save_plugin_config($formData)
{
// Make sure there are no duplicates in orders.
if (!validate_plugin_order($formData)) {
throw new PluginConfigOrderException();
}
$plugins = array();
$newEnabledPlugins = array();
foreach ($formData as $key => $data) {
if (startsWith($key, 'order')) {
continue;
}
// If there is no order, it means a disabled plugin has been enabled.
if (isset($formData['order_' . $key])) {
$plugins[(int) $formData['order_' . $key]] = $key;
}
else {
$newEnabledPlugins[] = $key;
}
}
// New enabled plugins will be added at the end of order.
$plugins = array_merge($plugins, $newEnabledPlugins);
// Sort plugins by order.
if (!ksort($plugins)) {
throw new PluginConfigOrderException();
}
$finalPlugins = array();
// Make plugins order continuous.
foreach ($plugins as $plugin) {
$finalPlugins[] = $plugin;
}
return $finalPlugins;
}
/**
* Validate plugin array submitted.
* Will fail if there is duplicate orders value.
*
* @param array $formData Data from submitted form.
*
* @return bool true if ok, false otherwise.
*/
function validate_plugin_order($formData)
{
$orders = array();
foreach ($formData as $key => $value) {
// No duplicate order allowed.
if (in_array($value, $orders)) {
return false;
}
if (startsWith($key, 'order')) {
$orders[] = $value;
}
}
return true;
}
/**
* Affect plugin parameters values into plugins array.
*
* @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
* @param mixed $config Plugins configuration.
*
* @return mixed Updated $plugins array.
*/
function load_plugin_parameter_values($plugins, $config)
{
$out = $plugins;
foreach ($plugins as $name => $plugin) {
if (empty($plugin['parameters'])) {
continue;
}
foreach ($plugin['parameters'] as $key => $param) {
if (!empty($config[$key])) {
$out[$name]['parameters'][$key] = $config[$key];
}
}
}
return $out;
}
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}
/**
* Exception used if an error occur while saving plugin configuration.
*/
class PluginConfigOrderException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'An error occurred while trying to save plugins loading order.';
}
}

View file

@ -9,11 +9,13 @@ class IOException extends Exception
/**
* Construct a new IOException
*
* @param string $path path to the ressource that cannot be accessed
* @param string $path path to the resource that cannot be accessed
* @param string $message Custom exception message.
*/
public function __construct($path)
public function __construct($path, $message = '')
{
$this->path = $path;
$this->message = 'Error accessing '.$this->path;
$this->message = empty($message) ? 'Error accessing' : $message;
$this->message .= PHP_EOL . $this->path;
}
}

View file

@ -14,13 +14,21 @@ class PageBuilder
*/
private $tpl;
/**
* @var ConfigManager $conf Configuration Manager instance.
*/
protected $conf;
/**
* PageBuilder constructor.
* $tpl is initialized at false for lazy loading.
*
* @param ConfigManager $conf Configuration Manager instance (reference).
*/
function __construct()
function __construct(&$conf)
{
$this->tpl = false;
$this->conf = $conf;
}
/**
@ -33,17 +41,17 @@ private function initialize()
try {
$version = ApplicationUtils::checkUpdate(
shaarli_version,
$GLOBALS['config']['UPDATECHECK_FILENAME'],
$GLOBALS['config']['UPDATECHECK_INTERVAL'],
$GLOBALS['config']['ENABLE_UPDATECHECK'],
$this->conf->get('resource.update_check'),
$this->conf->get('updates.check_updates_interval'),
$this->conf->get('updates.check_updates'),
isLoggedIn(),
$GLOBALS['config']['UPDATECHECK_BRANCH']
$this->conf->get('updates.check_updates_branch')
);
$this->tpl->assign('newVersion', escape($version));
$this->tpl->assign('versionError', '');
} catch (Exception $exc) {
logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage());
logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
$this->tpl->assign('newVersion', '');
$this->tpl->assign('versionError', escape($exc->getMessage()));
}
@ -62,19 +70,24 @@ private function initialize()
$this->tpl->assign('scripturl', index_url($_SERVER));
$this->tpl->assign('pagetitle', 'Shaarli');
$this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
if (!empty($GLOBALS['title'])) {
$this->tpl->assign('pagetitle', $GLOBALS['title']);
if ($this->conf->exists('general.title')) {
$this->tpl->assign('pagetitle', $this->conf->get('general.title'));
}
if (!empty($GLOBALS['titleLink'])) {
$this->tpl->assign('titleLink', $GLOBALS['titleLink']);
if ($this->conf->exists('general.header_link')) {
$this->tpl->assign('titleLink', $this->conf->get('general.header_link'));
}
if (!empty($GLOBALS['pagetitle'])) {
$this->tpl->assign('pagetitle', $GLOBALS['pagetitle']);
if ($this->conf->exists('pagetitle')) {
$this->tpl->assign('pagetitle', $this->conf->get('pagetitle'));
}
$this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']);
$this->tpl->assign('shaarlititle', $this->conf->get('title', 'Shaarli'));
$this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false));
$this->tpl->assign('showatom', $this->conf->get('feed.show_atom', false));
$this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
if (!empty($GLOBALS['plugin_errors'])) {
$this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']);
}
// To be removed with a proper theme configuration.
$this->tpl->assign('conf', $this->conf);
}
/**
@ -85,7 +98,6 @@ private function initialize()
*/
public function assign($placeholder, $value)
{
// Lazy initialization
if ($this->tpl === false) {
$this->initialize();
}
@ -101,7 +113,6 @@ public function assign($placeholder, $value)
*/
public function assignAll($data)
{
// Lazy initialization
if ($this->tpl === false) {
$this->initialize();
}
@ -113,6 +124,7 @@ public function assignAll($data)
foreach ($data as $key => $value) {
$this->assign($key, $value);
}
return true;
}
/**
@ -123,10 +135,10 @@ public function assignAll($data)
*/
public function renderPage($page)
{
// Lazy initialization
if ($this->tpl === false) {
$this->initialize();
}
$this->tpl->draw($page);
}

View file

@ -4,17 +4,9 @@
* Class PluginManager
*
* Use to manage, load and execute plugins.
*
* Using Singleton design pattern.
*/
class PluginManager
{
/**
* PluginManager singleton instance.
* @var PluginManager $instance
*/
private static $instance;
/**
* List of authorized plugins from configuration file.
* @var array $authorizedPlugins
@ -27,6 +19,11 @@ class PluginManager
*/
private $loadedPlugins = array();
/**
* @var ConfigManager Configuration Manager instance.
*/
protected $conf;
/**
* Plugins subdirectory.
* @var string $PLUGINS_PATH
@ -40,33 +37,13 @@ class PluginManager
public static $META_EXT = 'meta';
/**
* Private constructor: new instances not allowed.
*/
private function __construct()
{
}
/**
* Cloning isn't allowed either.
* Constructor.
*
* @return void
* @param ConfigManager $conf Configuration Manager instance.
*/
private function __clone()
public function __construct(&$conf)
{
}
/**
* Return existing instance of PluginManager, or create it.
*
* @return PluginManager instance.
*/
public static function getInstance()
{
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$instance;
$this->conf = $conf;
}
/**
@ -122,7 +99,7 @@ public function executeHooks($hook, &$data, $params = array())
$hookFunction = $this->buildHookName($hook, $plugin);
if (function_exists($hookFunction)) {
$data = call_user_func($hookFunction, $data);
$data = call_user_func($hookFunction, $data, $this->conf);
}
}
}
@ -148,6 +125,7 @@ private function loadPlugin($dir, $pluginName)
throw new PluginFileNotFoundException($pluginName);
}
$conf = $this->conf;
include_once $pluginFilePath;
$this->loadedPlugins[] = $pluginName;

View file

@ -12,16 +12,16 @@ class Updater
*/
protected $doneUpdates;
/**
* @var array Shaarli's configuration array.
*/
protected $config;
/**
* @var LinkDB instance.
*/
protected $linkDB;
/**
* @var ConfigManager $conf Configuration Manager instance.
*/
protected $conf;
/**
* @var bool True if the user is logged in, false otherwise.
*/
@ -36,15 +36,15 @@ class Updater
* Object constructor.
*
* @param array $doneUpdates Updates which are already done.
* @param array $config Shaarli's configuration array.
* @param LinkDB $linkDB LinkDB instance.
* @oaram ConfigManager $conf Configuration Manager instance.
* @param boolean $isLoggedIn True if the user is logged in.
*/
public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn)
public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
{
$this->doneUpdates = $doneUpdates;
$this->config = $config;
$this->linkDB = $linkDB;
$this->conf = $conf;
$this->isLoggedIn = $isLoggedIn;
// Retrieve all update methods.
@ -114,19 +114,19 @@ public function getDoneUpdates()
*/
public function updateMethodMergeDeprecatedConfigFile()
{
$config_file = $this->config['config']['CONFIG_FILE'];
if (is_file($this->config['config']['DATADIR'].'/options.php')) {
include $this->config['config']['DATADIR'].'/options.php';
if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
include $this->conf->get('resource.data_dir') . '/options.php';
// Load GLOBALS into config
$allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
$allowedKeys[] = 'config';
foreach ($GLOBALS as $key => $value) {
$this->config[$key] = $value;
if (in_array($key, $allowedKeys)) {
$this->conf->set($key, $value);
}
$this->config['config']['CONFIG_FILE'] = $config_file;
writeConfig($this->config, $this->isLoggedIn);
unlink($this->config['config']['DATADIR'].'/options.php');
}
$this->conf->write($this->isLoggedIn);
unlink($this->conf->get('resource.data_dir').'/options.php');
}
return true;
@ -143,7 +143,76 @@ public function updateMethodRenameDashTags()
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
$this->linkDB[$link['linkdate']] = $link;
}
$this->linkDB->savedb($this->config['config']['PAGECACHE']);
$this->linkDB->savedb($this->conf->get('resource.page_cache'));
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.php'.
* It will also convert legacy setting keys to the new ones.
*/
public function updateMethodConfigToJson()
{
// JSON config already exists, nothing to do.
if ($this->conf->getConfigIO() instanceof ConfigJson) {
return true;
}
$configPhp = new ConfigPhp();
$configJson = new ConfigJson();
$oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
$this->conf->setConfigIO($configJson);
$this->conf->reload();
$legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
foreach (ConfigPhp::$ROOT_KEYS as $key) {
$this->conf->set($legacyMap[$key], $oldConfig[$key]);
}
// Set sub config keys (config and plugins)
$subConfig = array('config', 'plugins');
foreach ($subConfig as $sub) {
foreach ($oldConfig[$sub] as $key => $value) {
if (isset($legacyMap[$sub .'.'. $key])) {
$configKey = $legacyMap[$sub .'.'. $key];
} else {
$configKey = $sub .'.'. $key;
}
$this->conf->set($configKey, $value);
}
}
try{
$this->conf->write($this->isLoggedIn);
return true;
} catch (IOException $e) {
error_log($e->getMessage());
return false;
}
}
/**
* Escape settings which have been manually escaped in every request in previous versions:
* - general.title
* - general.header_link
* - extras.redirector
*
* @return bool true if the update is successful, false otherwise.
*/
public function escapeUnescapedConfig()
{
try {
$this->conf->set('general.title', escape($this->conf->get('general.title')));
$this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
$this->conf->set('redirector.url', escape($this->conf->get('redirector.url')));
$this->conf->write($this->isLoggedIn);
} catch (Exception $e) {
error_log($e->getMessage());
return false;
}
return true;
}
}
@ -203,7 +272,6 @@ private function buildMessage($message)
}
}
/**
* Read the updates file, and return already done updates.
*

View file

@ -0,0 +1,33 @@
<?php
/**
* Interface ConfigIO
*
* This describes how Config types should store their configuration.
*/
interface ConfigIO
{
/**
* Read configuration.
*
* @param string $filepath Config file absolute path.
*
* @return array All configuration in an array.
*/
function read($filepath);
/**
* Write configuration.
*
* @param string $filepath Config file absolute path.
* @param array $conf All configuration in an array.
*/
function write($filepath, $conf);
/**
* Get config file extension according to config type.
*
* @return string Config file extension.
*/
function getExtension();
}

View file

@ -0,0 +1,78 @@
<?php
/**
* Class ConfigJson (ConfigIO implementation)
*
* Handle Shaarli's JSON configuration file.
*/
class ConfigJson implements ConfigIO
{
/**
* @inheritdoc
*/
function read($filepath)
{
if (! is_readable($filepath)) {
return array();
}
$data = file_get_contents($filepath);
$data = str_replace(self::getPhpHeaders(), '', $data);
$data = str_replace(self::getPhpSuffix(), '', $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::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix();
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';
}
/**
* 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.
*
* Note: this isn't a static field because concatenation isn't supported in field declaration before PHP 5.6.
*
* @return string PHP start tag and comment tag.
*/
public static function getPhpHeaders()
{
return '<?php /*'. PHP_EOL;
}
/**
* Get PHP comment closing tags.
*
* Static method for consistency with getPhpHeaders.
*
* @return string PHP comment closing.
*/
public static function getPhpSuffix()
{
return PHP_EOL . '*/ ?>';
}
}

View file

@ -0,0 +1,392 @@
<?php
// FIXME! Namespaces...
require_once 'ConfigIO.php';
require_once 'ConfigJson.php';
require_once 'ConfigPhp.php';
/**
* Class ConfigManager
*
* Manages all Shaarli's settings.
* See the documentation for more information on settings:
* - doc/Shaarli-configuration.html
* - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration
*/
class ConfigManager
{
/**
* @var string Flag telling a setting is not found.
*/
protected static $NOT_FOUND = 'NOT_FOUND';
/**
* @var string Config folder.
*/
protected $configFile;
/**
* @var array Loaded config array.
*/
protected $loadedConfig;
/**
* @var ConfigIO implementation instance.
*/
protected $configIO;
/**
* Constructor.
*/
public function __construct($configFile = 'data/config')
{
$this->configFile = $configFile;
$this->initialize();
}
/**
* Reset the ConfigManager instance.
*/
public function reset()
{
$this->initialize();
}
/**
* Rebuild the loaded config array from config files.
*/
public function reload()
{
$this->load();
}
/**
* Initialize the ConfigIO and loaded the conf.
*/
protected function initialize()
{
if (file_exists($this->configFile . '.php')) {
$this->configIO = new ConfigPhp();
} else {
$this->configIO = new ConfigJson();
}
$this->load();
}
/**
* Load configuration in the ConfigurationManager.
*/
protected function load()
{
$this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
$this->setDefaultValues();
}
/**
* Get a setting.
*
* Supports nested settings with dot separated keys.
* Eg. 'config.stuff.option' will find $conf[config][stuff][option],
* or in JSON:
* { "config": { "stuff": {"option": "mysetting" } } } }
*
* @param string $setting Asked setting, keys separated with dots.
* @param string $default Default value if not found.
*
* @return mixed Found setting, or the default value.
*/
public function get($setting, $default = '')
{
// During the ConfigIO transition, map legacy settings to the new ones.
if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
$setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
}
$settings = explode('.', $setting);
$value = self::getConfig($settings, $this->loadedConfig);
if ($value === self::$NOT_FOUND) {
return $default;
}
return $value;
}
/**
* Set a setting, and eventually write it.
*
* Supports nested settings with dot separated keys.
*
* @param string $setting Asked setting, keys separated with dots.
* @param string $value Value to set.
* @param bool $write Write the new setting in the config file, default false.
* @param bool $isLoggedIn User login state, default false.
*
* @throws Exception Invalid
*/
public function set($setting, $value, $write = false, $isLoggedIn = false)
{
if (empty($setting) || ! is_string($setting)) {
throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
}
// During the ConfigIO transition, map legacy settings to the new ones.
if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
$setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
}
$settings = explode('.', $setting);
self::setConfig($settings, $value, $this->loadedConfig);
if ($write) {
$this->write($isLoggedIn);
}
}
/**
* Check if a settings exists.
*
* Supports nested settings with dot separated keys.
*
* @param string $setting Asked setting, keys separated with dots.
*
* @return bool true if the setting exists, false otherwise.
*/
public function exists($setting)
{
// During the ConfigIO transition, map legacy settings to the new ones.
if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
$setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
}
$settings = explode('.', $setting);
$value = self::getConfig($settings, $this->loadedConfig);
if ($value === self::$NOT_FOUND) {
return false;
}
return true;
}
/**
* Call the config writer.
*
* @param bool $isLoggedIn User login state.
*
* @return bool True if the configuration has been successfully written, false otherwise.
*
* @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
* @throws UnauthorizedConfigException: user is not authorize to change configuration.
* @throws IOException: an error occurred while writing the new config file.
*/
public function write($isLoggedIn)
{
// These fields are required in configuration.
$mandatoryFields = array(
'credentials.login',
'credentials.hash',
'credentials.salt',
'security.session_protection_disabled',
'general.timezone',
'general.title',
'general.header_link',
'privacy.default_private_links',
'redirector.url',
);
// Only logged in user can alter config.
if (is_file($this->getConfigFileExt()) && !$isLoggedIn) {
throw new UnauthorizedConfigException();
}
// Check that all mandatory fields are provided in $conf.
foreach ($mandatoryFields as $field) {
if (! $this->exists($field)) {
throw new MissingFieldConfigException($field);
}
}
return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig);
}
/**
* Set the config file path (without extension).
*
* @param string $configFile File path.
*/
public function setConfigFile($configFile)
{
$this->configFile = $configFile;
}
/**
* Return the configuration file path (without extension).
*
* @return string Config path.
*/
public function getConfigFile()
{
return $this->configFile;
}
/**
* Get the configuration file path with its extension.
*
* @return string Config file path.
*/
public function getConfigFileExt()
{
return $this->configFile . $this->configIO->getExtension();
}
/**
* Recursive function which find asked setting in the loaded config.
*
* @param array $settings Ordered array which contains keys to find.
* @param array $conf Loaded settings, then sub-array.
*
* @return mixed Found setting or NOT_FOUND flag.
*/
protected static function getConfig($settings, $conf)
{
if (!is_array($settings) || count($settings) == 0) {
return self::$NOT_FOUND;
}
$setting = array_shift($settings);
if (!isset($conf[$setting])) {
return self::$NOT_FOUND;
}
if (count($settings) > 0) {
return self::getConfig($settings, $conf[$setting]);
}
return $conf[$setting];
}
/**
* Recursive function which find asked setting in the loaded config.
*
* @param array $settings Ordered array which contains keys to find.
* @param mixed $value
* @param array $conf Loaded settings, then sub-array.
*
* @return mixed Found setting or NOT_FOUND flag.
*/
protected static function setConfig($settings, $value, &$conf)
{
if (!is_array($settings) || count($settings) == 0) {
return self::$NOT_FOUND;
}
$setting = array_shift($settings);
if (count($settings) > 0) {
return self::setConfig($settings, $value, $conf[$setting]);
}
$conf[$setting] = $value;
}
/**
* Set a bunch of default values allowing Shaarli to start without a config file.
*/
protected function setDefaultValues()
{
$this->setEmpty('resource.data_dir', 'data');
$this->setEmpty('resource.config', 'data/config.php');
$this->setEmpty('resource.datastore', 'data/datastore.php');
$this->setEmpty('resource.ban_file', 'data/ipbans.php');
$this->setEmpty('resource.updates', 'data/updates.txt');
$this->setEmpty('resource.log', 'data/log.txt');
$this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
$this->setEmpty('resource.raintpl_tpl', 'tpl/');
$this->setEmpty('resource.raintpl_tmp', 'tmp/');
$this->setEmpty('resource.thumbnails_cache', 'cache');
$this->setEmpty('resource.page_cache', 'pagecache');
$this->setEmpty('security.ban_after', 4);
$this->setEmpty('security.ban_duration', 1800);
$this->setEmpty('security.session_protection_disabled', false);
$this->setEmpty('security.open_shaarli', false);
$this->setEmpty('general.header_link', '?');
$this->setEmpty('general.links_per_page', 20);
$this->setEmpty('general.enabled_plugins', array('qrcode'));
$this->setEmpty('updates.check_updates', false);
$this->setEmpty('updates.check_updates_branch', 'stable');
$this->setEmpty('updates.check_updates_interval', 86400);
$this->setEmpty('feed.rss_permalinks', true);
$this->setEmpty('feed.show_atom', false);
$this->setEmpty('privacy.default_private_links', false);
$this->setEmpty('privacy.hide_public_links', false);
$this->setEmpty('privacy.hide_timestamps', false);
$this->setEmpty('thumbnail.enable_thumbnails', true);
$this->setEmpty('thumbnail.enable_localcache', true);
$this->setEmpty('redirector.url', '');
$this->setEmpty('redirector.encode_url', true);
$this->setEmpty('plugins', array());
}
/**
* Set only if the setting does not exists.
*
* @param string $key Setting key.
* @param mixed $value Setting value.
*/
public function setEmpty($key, $value)
{
if (! $this->exists($key)) {
$this->set($key, $value);
}
}
/**
* @return ConfigIO
*/
public function getConfigIO()
{
return $this->configIO;
}
/**
* @param ConfigIO $configIO
*/
public function setConfigIO($configIO)
{
$this->configIO = $configIO;
}
}
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}

View file

@ -0,0 +1,132 @@
<?php
/**
* Class ConfigPhp (ConfigIO implementation)
*
* Handle Shaarli's legacy PHP configuration file.
* Note: this is only designed to support the transition to JSON configuration.
*/
class ConfigPhp implements ConfigIO
{
/**
* @var array List of config key without group.
*/
public static $ROOT_KEYS = array(
'login',
'hash',
'salt',
'timezone',
'title',
'titleLink',
'redirector',
'disablesessionprotection',
'privateLinkByDefault',
);
/**
* Map legacy config keys with the new ones.
* If ConfigPhp is used, getting <newkey> will actually look for <legacykey>.
* The Updater will use this array to transform keys when switching to JSON.
*
* @var array current key => legacy key.
*/
public static $LEGACY_KEYS_MAPPING = array(
'credentials.login' => 'login',
'credentials.hash' => 'hash',
'credentials.salt' => 'salt',
'resource.data_dir' => 'config.DATADIR',
'resource.config' => 'config.CONFIG_FILE',
'resource.datastore' => 'config.DATASTORE',
'resource.updates' => 'config.UPDATES_FILE',
'resource.log' => 'config.LOG_FILE',
'resource.update_check' => 'config.UPDATECHECK_FILENAME',
'resource.raintpl_tpl' => 'config.RAINTPL_TPL',
'resource.raintpl_tmp' => 'config.RAINTPL_TMP',
'resource.thumbnails_cache' => 'config.CACHEDIR',
'resource.page_cache' => 'config.PAGECACHE',
'resource.ban_file' => 'config.IPBANS_FILENAME',
'security.session_protection_disabled' => 'disablesessionprotection',
'security.ban_after' => 'config.BAN_AFTER',
'security.ban_duration' => 'config.BAN_DURATION',
'general.title' => 'title',
'general.timezone' => 'timezone',
'general.header_link' => 'titleLink',
'updates.check_updates' => 'config.ENABLE_UPDATECHECK',
'updates.check_updates_branch' => 'config.UPDATECHECK_BRANCH',
'updates.check_updates_interval' => 'config.UPDATECHECK_INTERVAL',
'privacy.default_private_links' => 'privateLinkByDefault',
'feed.rss_permalinks' => 'config.ENABLE_RSS_PERMALINKS',
'general.links_per_page' => 'config.LINKS_PER_PAGE',
'thumbnail.enable_thumbnails' => 'config.ENABLE_THUMBNAILS',
'thumbnail.enable_localcache' => 'config.ENABLE_LOCALCACHE',
'general.enabled_plugins' => 'config.ENABLED_PLUGINS',
'redirector.url' => 'redirector',
'redirector.encode_url' => 'config.REDIRECTOR_URLENCODE',
'feed.show_atom' => 'config.SHOW_ATOM',
'privacy.hide_public_links' => 'config.HIDE_PUBLIC_LINKS',
'privacy.hide_timestamps' => 'config.HIDE_TIMESTAMPS',
'security.open_shaarli' => 'config.OPEN_SHAARLI',
);
/**
* @inheritdoc
*/
function read($filepath)
{
if (! file_exists($filepath) || ! is_readable($filepath)) {
return array();
}
include $filepath;
$out = array();
foreach (self::$ROOT_KEYS as $key) {
$out[$key] = $GLOBALS[$key];
}
$out['config'] = $GLOBALS['config'];
$out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array();
return $out;
}
/**
* @inheritdoc
*/
function write($filepath, $conf)
{
$configStr = '<?php '. PHP_EOL;
foreach (self::$ROOT_KEYS as $key) {
if (isset($conf[$key])) {
$configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL;
}
}
// Store all $conf['config']
foreach ($conf['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL;
}
if (isset($conf['plugins'])) {
foreach ($conf['plugins'] as $key => $value) {
$configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL;
}
}
if (!file_put_contents($filepath, $configStr)
|| strcmp(file_get_contents($filepath), $configStr) != 0
) {
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 '.php';
}
}

View file

@ -0,0 +1,120 @@
<?php
/**
* Plugin configuration helper functions.
*
* Note: no access to configuration files here.
*/
/**
* Process plugin administration form data and save it in an array.
*
* @param array $formData Data sent by the plugin admin form.
*
* @return array New list of enabled plugin, ordered.
*
* @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
*/
function save_plugin_config($formData)
{
// Make sure there are no duplicates in orders.
if (!validate_plugin_order($formData)) {
throw new PluginConfigOrderException();
}
$plugins = array();
$newEnabledPlugins = array();
foreach ($formData as $key => $data) {
if (startsWith($key, 'order')) {
continue;
}
// If there is no order, it means a disabled plugin has been enabled.
if (isset($formData['order_' . $key])) {
$plugins[(int) $formData['order_' . $key]] = $key;
}
else {
$newEnabledPlugins[] = $key;
}
}
// New enabled plugins will be added at the end of order.
$plugins = array_merge($plugins, $newEnabledPlugins);
// Sort plugins by order.
if (!ksort($plugins)) {
throw new PluginConfigOrderException();
}
$finalPlugins = array();
// Make plugins order continuous.
foreach ($plugins as $plugin) {
$finalPlugins[] = $plugin;
}
return $finalPlugins;
}
/**
* Validate plugin array submitted.
* Will fail if there is duplicate orders value.
*
* @param array $formData Data from submitted form.
*
* @return bool true if ok, false otherwise.
*/
function validate_plugin_order($formData)
{
$orders = array();
foreach ($formData as $key => $value) {
// No duplicate order allowed.
if (in_array($value, $orders)) {
return false;
}
if (startsWith($key, 'order')) {
$orders[] = $value;
}
}
return true;
}
/**
* Affect plugin parameters values into plugins array.
*
* @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
* @param mixed $conf Plugins configuration.
*
* @return mixed Updated $plugins array.
*/
function load_plugin_parameter_values($plugins, $conf)
{
$out = $plugins;
foreach ($plugins as $name => $plugin) {
if (empty($plugin['parameters'])) {
continue;
}
foreach ($plugin['parameters'] as $key => $param) {
if (!empty($conf[$key])) {
$out[$name]['parameters'][$key] = $conf[$key];
}
}
}
return $out;
}
/**
* Exception used if an error occur while saving plugin configuration.
*/
class PluginConfigOrderException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'An error occurred while trying to save plugins loading order.';
}
}

683
index.php

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
<?php
$GLOBALS['plugins']['READITYOUSELF_URL'] = 'http://someurl.com';

View file

@ -8,34 +8,31 @@
// it seems kinda dead.
// Not tested.
// don't raise unnecessary warnings
if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) {
include PluginManager::$PLUGINS_PATH . '/readityourself/config.php';
}
if (empty($GLOBALS['plugins']['READITYOUSELF_URL'])) {
$riyUrl = $conf->get('plugins.READITYOUSELF_URL');
if (empty($riyUrl)) {
$GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '.
'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '.
'in "plugins/readityourself/config.php" or in your Shaarli config.php file.';
'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
}
/**
* Add readityourself icon to link_plugin when rendering linklist.
*
* @param mixed $data - linklist data.
* @param mixed $data Linklist data.
* @param ConfigManager $conf Configuration Manager instance.
*
* @return mixed - linklist data with readityourself plugin.
*/
function hook_readityourself_render_linklist($data)
function hook_readityourself_render_linklist($data, $conf)
{
if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) {
$riyUrl = $conf->get('plugins.READITYOUSELF_URL');
if (empty($riyUrl)) {
return $data;
}
$readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
foreach ($data['links'] as &$value) {
$readityourself = sprintf($readityourself_html, $GLOBALS['plugins']['READITYOUSELF_URL'], $value['url'], PluginManager::$PLUGINS_PATH);
$readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
$value['link_plugin'][] = $readityourself;
}

View file

@ -12,31 +12,26 @@ The directory structure should look like:
└── plugins
   └── wallabag
   ├── README.md
├── config.php.dist
   ├── wallabag.html
   ├── wallabag.meta
   ├── wallabag.php
   └── wallabag.png
   ├── wallabag.php
   └── WallabagInstance.php
```
To enable the plugin, add `'wallabag'` to your list of enabled plugins in `data/options.php` (`PLUGINS` array).
This should look like:
To enable the plugin, you can either:
```
$GLOBALS['config']['PLUGINS'] = array('qrcode', 'any_other_plugin', 'wallabag')
```
* enable it in the plugins administration page (`?do=pluginadmin`).
* add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section).
### Configuration
Copy `config.php.dist` into `config.php` and setup your instance.
Go to the plugin administration page, and edit the following settings (with the plugin enabled).
*Wallabag instance URL*
```
$GLOBALS['config']['WALLABAG_URL'] = 'http://v2.wallabag.org' ;
```
**WALLABAG_URL**: *Wallabag instance URL*
Example value: `http://v2.wallabag.org`
*Wallabag version*: either `1` (for 1.x) or `2` (for 2.x)
```
$GLOBALS['config']['WALLABAG_VERSION'] = 2;
```
**WALLABAG_VERSION**: *Wallabag version*
Value: either `1` (for 1.x) or `2` (for 2.x)
> Note: these settings can also be set in `data/config.php`.
> Note: these settings can also be set in `data/config.json.php`, in the plugins section.

View file

@ -1,4 +0,0 @@
<?php
$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org';
$GLOBALS['plugins']['WALLABAG_VERSION'] = 1;

View file

@ -6,34 +6,29 @@
require_once 'WallabagInstance.php';
// don't raise unnecessary warnings
if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) {
include PluginManager::$PLUGINS_PATH . '/wallabag/config.php';
}
if (empty($GLOBALS['plugins']['WALLABAG_URL'])) {
$wallabagUrl = $conf->get('plugins.WALLABAG_URL');
if (empty($wallabagUrl)) {
$GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '.
'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '.
'in "plugins/wallabag/config.php" or in your Shaarli config.php file.';
'Please define the "WALLABAG_URL" setting in the plugin administration page.';
}
/**
* Add wallabag icon to link_plugin when rendering linklist.
*
* @param mixed $data - linklist data.
* @param mixed $data Linklist data.
* @param ConfigManager $conf Configuration Manager instance.
*
* @return mixed - linklist data with wallabag plugin.
*/
function hook_wallabag_render_linklist($data)
function hook_wallabag_render_linklist($data, $conf)
{
if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) {
$wallabagUrl = $conf->get('plugins.WALLABAG_URL');
if (empty($wallabagUrl)) {
return $data;
}
$version = isset($GLOBALS['plugins']['WALLABAG_VERSION'])
? $GLOBALS['plugins']['WALLABAG_VERSION']
: '';
$wallabagInstance = new WallabagInstance($GLOBALS['plugins']['WALLABAG_URL'], $version);
$version = $conf->get('plugins.WALLABAG_VERSION');
$wallabagInstance = new WallabagInstance($wallabagUrl, $version);
$wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');

View file

@ -3,6 +3,7 @@
* ApplicationUtils' tests
*/
require_once 'application/config/ConfigManager.php';
require_once 'application/ApplicationUtils.php';
/**
@ -59,7 +60,7 @@ public function testGetLatestGitVersionCode()
$testTimeout
)
);
$this->assertRegexp(
$this->assertRegExp(
self::$versionPattern,
ApplicationUtils::getLatestGitVersionCode(
'https://raw.githubusercontent.com/shaarli/Shaarli/'
@ -275,21 +276,21 @@ public function testCheckSupportedPHPVersion52()
*/
public function testCheckCurrentResourcePermissions()
{
$config = array(
'CACHEDIR' => 'cache',
'CONFIG_FILE' => 'data/config.php',
'DATADIR' => 'data',
'DATASTORE' => 'data/datastore.php',
'IPBANS_FILENAME' => 'data/ipbans.php',
'LOG_FILE' => 'data/log.txt',
'PAGECACHE' => 'pagecache',
'RAINTPL_TMP' => 'tmp',
'RAINTPL_TPL' => 'tpl',
'UPDATECHECK_FILENAME' => 'data/lastupdatecheck.txt'
);
$conf = new ConfigManager('');
$conf->set('resource.thumbnails_cache', 'cache');
$conf->set('resource.config', 'data/config.php');
$conf->set('resource.data_dir', 'data');
$conf->set('resource.datastore', 'data/datastore.php');
$conf->set('resource.ban_file', 'data/ipbans.php');
$conf->set('resource.log', 'data/log.txt');
$conf->set('resource.page_cache', 'pagecache');
$conf->set('resource.raintpl_tmp', 'tmp');
$conf->set('resource.raintpl_tpl', 'tpl');
$conf->set('resource.update_check', 'data/lastupdatecheck.txt');
$this->assertEquals(
array(),
ApplicationUtils::checkResourcePermissions($config)
ApplicationUtils::checkResourcePermissions($conf)
);
}
@ -298,18 +299,17 @@ public function testCheckCurrentResourcePermissions()
*/
public function testCheckCurrentResourcePermissionsErrors()
{
$config = array(
'CACHEDIR' => 'null/cache',
'CONFIG_FILE' => 'null/data/config.php',
'DATADIR' => 'null/data',
'DATASTORE' => 'null/data/store.php',
'IPBANS_FILENAME' => 'null/data/ipbans.php',
'LOG_FILE' => 'null/data/log.txt',
'PAGECACHE' => 'null/pagecache',
'RAINTPL_TMP' => 'null/tmp',
'RAINTPL_TPL' => 'null/tpl',
'UPDATECHECK_FILENAME' => 'null/data/lastupdatecheck.txt'
);
$conf = new ConfigManager('');
$conf->set('resource.thumbnails_cache', 'null/cache');
$conf->set('resource.config', 'null/data/config.php');
$conf->set('resource.data_dir', 'null/data');
$conf->set('resource.datastore', 'null/data/store.php');
$conf->set('resource.ban_file', 'null/data/ipbans.php');
$conf->set('resource.log', 'null/data/log.txt');
$conf->set('resource.page_cache', 'null/pagecache');
$conf->set('resource.raintpl_tmp', 'null/tmp');
$conf->set('resource.raintpl_tpl', 'null/tpl');
$conf->set('resource.update_check', 'null/data/lastupdatecheck.txt');
$this->assertEquals(
array(
'"null/tpl" directory is not readable',
@ -322,7 +322,7 @@ public function testCheckCurrentResourcePermissionsErrors()
'"null/tmp" directory is not readable',
'"null/tmp" directory is not writable'
),
ApplicationUtils::checkResourcePermissions($config)
ApplicationUtils::checkResourcePermissions($conf)
);
}
}

View file

@ -1,244 +0,0 @@
<?php
/**
* Config' tests
*/
require_once 'application/Config.php';
/**
* Unitary tests for Shaarli config related functions
*/
class ConfigTest extends PHPUnit_Framework_TestCase
{
// Configuration input set.
private static $configFields;
/**
* Executed before each test.
*/
public function setUp()
{
self::$configFields = array(
'login' => 'login',
'hash' => 'hash',
'salt' => 'salt',
'timezone' => 'Europe/Paris',
'title' => 'title',
'titleLink' => 'titleLink',
'redirector' => '',
'disablesessionprotection' => false,
'privateLinkByDefault' => false,
'config' => array(
'CONFIG_FILE' => 'tests/config.php',
'DATADIR' => 'tests',
'config1' => 'config1data',
'config2' => 'config2data',
)
);
}
/**
* Executed after each test.
*
* @return void
*/
public function tearDown()
{
if (is_file(self::$configFields['config']['CONFIG_FILE'])) {
unlink(self::$configFields['config']['CONFIG_FILE']);
}
}
/**
* Test writeConfig function, valid use case, while being logged in.
*/
public function testWriteConfig()
{
writeConfig(self::$configFields, true);
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
$this->assertEquals(self::$configFields['hash'], $GLOBALS['hash']);
$this->assertEquals(self::$configFields['salt'], $GLOBALS['salt']);
$this->assertEquals(self::$configFields['timezone'], $GLOBALS['timezone']);
$this->assertEquals(self::$configFields['title'], $GLOBALS['title']);
$this->assertEquals(self::$configFields['titleLink'], $GLOBALS['titleLink']);
$this->assertEquals(self::$configFields['redirector'], $GLOBALS['redirector']);
$this->assertEquals(self::$configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
$this->assertEquals(self::$configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
$this->assertEquals(self::$configFields['config']['config1'], $GLOBALS['config']['config1']);
$this->assertEquals(self::$configFields['config']['config2'], $GLOBALS['config']['config2']);
}
/**
* Test writeConfig option while logged in:
* 1. init fields.
* 2. update fields, add new sub config, add new root config.
* 3. rewrite config.
* 4. check result.
*/
public function testWriteConfigFieldUpdate()
{
writeConfig(self::$configFields, true);
self::$configFields['title'] = 'ok';
self::$configFields['config']['config1'] = 'ok';
self::$configFields['config']['config_new'] = 'ok';
self::$configFields['new'] = 'should not be saved';
writeConfig(self::$configFields, true);
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals('ok', $GLOBALS['title']);
$this->assertEquals('ok', $GLOBALS['config']['config1']);
$this->assertEquals('ok', $GLOBALS['config']['config_new']);
$this->assertFalse(isset($GLOBALS['new']));
}
/**
* Test writeConfig function with an empty array.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigEmpty()
{
writeConfig(array(), true);
}
/**
* Test writeConfig function with a missing mandatory field.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigMissingField()
{
unset(self::$configFields['login']);
writeConfig(self::$configFields, true);
}
/**
* Test writeConfig function while being logged out, and there is no config file existing.
*/
public function testWriteConfigLoggedOutNoFile()
{
writeConfig(self::$configFields, false);
}
/**
* Test writeConfig function while being logged out, and a config file already exists.
*
* @expectedException UnauthorizedConfigException
*/
public function testWriteConfigLoggedOutWithFile()
{
file_put_contents(self::$configFields['config']['CONFIG_FILE'], '');
writeConfig(self::$configFields, false);
}
/**
* Test save_plugin_config with valid data.
*
* @throws PluginConfigOrderException
*/
public function testSavePluginConfigValid()
{
$data = array(
'order_plugin1' => 2, // no plugin related
'plugin2' => 0, // new - at the end
'plugin3' => 0, // 2nd
'order_plugin3' => 8,
'plugin4' => 0, // 1st
'order_plugin4' => 5,
);
$expected = array(
'plugin3',
'plugin4',
'plugin2',
);
$out = save_plugin_config($data);
$this->assertEquals($expected, $out);
}
/**
* Test save_plugin_config with invalid data.
*
* @expectedException PluginConfigOrderException
*/
public function testSavePluginConfigInvalid()
{
$data = array(
'plugin2' => 0,
'plugin3' => 0,
'order_plugin3' => 0,
'plugin4' => 0,
'order_plugin4' => 0,
);
save_plugin_config($data);
}
/**
* Test save_plugin_config without data.
*/
public function testSavePluginConfigEmpty()
{
$this->assertEquals(array(), save_plugin_config(array()));
}
/**
* Test validate_plugin_order with valid data.
*/
public function testValidatePluginOrderValid()
{
$data = array(
'order_plugin1' => 2,
'plugin2' => 0,
'plugin3' => 0,
'order_plugin3' => 1,
'plugin4' => 0,
'order_plugin4' => 5,
);
$this->assertTrue(validate_plugin_order($data));
}
/**
* Test validate_plugin_order with invalid data.
*/
public function testValidatePluginOrderInvalid()
{
$data = array(
'order_plugin1' => 2,
'order_plugin3' => 1,
'order_plugin4' => 1,
);
$this->assertFalse(validate_plugin_order($data));
}
/**
* Test load_plugin_parameter_values.
*/
public function testLoadPluginParameterValues()
{
$plugins = array(
'plugin_name' => array(
'parameters' => array(
'param1' => true,
'param2' => false,
'param3' => '',
)
)
);
$parameters = array(
'param1' => 'value1',
'param2' => 'value2',
);
$result = load_plugin_parameter_values($plugins, $parameters);
$this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
$this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
$this->assertEquals('', $result['plugin_name']['parameters']['param3']);
}
}

View file

@ -76,7 +76,7 @@ public function testRSSBuildData()
// Test headers (RSS)
$this->assertEquals(self::$RSS_LANGUAGE, $data['language']);
$this->assertEmpty($data['pubsubhub_url']);
$this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']);
$this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $data['last_update']);
$this->assertEquals(true, $data['show_dates']);
$this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']);
$this->assertEquals('http://host.tld/', $data['index_url']);
@ -88,7 +88,7 @@ public function testRSSBuildData()
$this->assertEquals('20150310_114651', $link['linkdate']);
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
$this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']);
$this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['iso_date']);
$this->assertContains('Stallman has a beard', $link['description']);
$this->assertContains('Permalink', $link['description']);
$this->assertContains('http://host.tld/?WDWyig', $link['description']);
@ -113,7 +113,7 @@ public function testAtomBuildData()
$data = $feedBuilder->buildData();
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
$link = array_shift($data['links']);
$this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']);
$this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:+\d{2}/', $link['iso_date']);
}
/**

View file

@ -101,7 +101,7 @@ public function testConstructLoggedOut()
* Attempt to instantiate a LinkDB whereas the datastore is not writable
*
* @expectedException IOException
* @expectedExceptionMessageRegExp /Error accessing null/
* @expectedExceptionMessageRegExp /Error accessing\nnull/
*/
public function testConstructDatastoreNotWriteable()
{

View file

@ -23,6 +23,17 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
*/
private static $pluginName = 'test';
/**
* @var PluginManager $pluginManager Plugin Mananger instance.
*/
protected $pluginManager;
public function setUp()
{
$conf = new ConfigManager('');
$this->pluginManager = new PluginManager($conf);
}
/**
* Test plugin loading and hook execution.
*
@ -30,23 +41,21 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
*/
public function testPlugin()
{
$pluginManager = PluginManager::getInstance();
PluginManager::$PLUGINS_PATH = self::$pluginPath;
$pluginManager->load(array(self::$pluginName));
$this->pluginManager->load(array(self::$pluginName));
$this->assertTrue(function_exists('hook_test_random'));
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data);
$this->pluginManager->executeHooks('random', $data);
$this->assertEquals('woot', $data[1]);
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data, array('target' => 'test'));
$this->pluginManager->executeHooks('random', $data, array('target' => 'test'));
$this->assertEquals('page test', $data[1]);
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data, array('loggedin' => true));
$this->pluginManager->executeHooks('random', $data, array('loggedin' => true));
$this->assertEquals('loggedin', $data[1]);
}
@ -57,11 +66,8 @@ public function testPlugin()
*/
public function testPluginNotFound()
{
$pluginManager = PluginManager::getInstance();
$pluginManager->load(array());
$pluginManager->load(array('nope', 'renope'));
$this->pluginManager->load(array());
$this->pluginManager->load(array('nope', 'renope'));
}
/**
@ -69,16 +75,14 @@ public function testPluginNotFound()
*/
public function testGetPluginsMeta()
{
$pluginManager = PluginManager::getInstance();
PluginManager::$PLUGINS_PATH = self::$pluginPath;
$pluginManager->load(array(self::$pluginName));
$this->pluginManager->load(array(self::$pluginName));
$expectedParameters = array(
'pop' => '',
'hip' => '',
);
$meta = $pluginManager->getPluginsMeta();
$meta = $this->pluginManager->getPluginsMeta();
$this->assertEquals('test plugin', $meta[self::$pluginName]['description']);
$this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']);
}

View file

@ -12,13 +12,13 @@ class DummyUpdater extends Updater
* Object constructor.
*
* @param array $doneUpdates Updates which are already done.
* @param array $config Shaarli's configuration array.
* @param LinkDB $linkDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance.
* @param boolean $isLoggedIn True if the user is logged in.
*/
public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn)
public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
{
parent::__construct($doneUpdates, $config, $linkDB, $isLoggedIn);
parent::__construct($doneUpdates, $linkDB, $conf, $isLoggedIn);
// Retrieve all update methods.
// For unit test, only retrieve final methods,

View file

@ -1,5 +1,6 @@
<?php
require_once 'application/config/ConfigManager.php';
require_once 'tests/Updater/DummyUpdater.php';
/**
@ -8,59 +9,27 @@
*/
class UpdaterTest extends PHPUnit_Framework_TestCase
{
/**
* @var array Configuration input set.
*/
private static $configFields;
/**
* @var string Path to test datastore.
*/
protected static $testDatastore = 'sandbox/datastore.php';
/**
* @var string Config file path (without extension).
*/
protected static $configFile = 'tests/utils/config/configJson';
/**
* @var ConfigManager
*/
protected $conf;
/**
* Executed before each test.
*/
public function setUp()
{
self::$configFields = array(
'login' => 'login',
'hash' => 'hash',
'salt' => 'salt',
'timezone' => 'Europe/Paris',
'title' => 'title',
'titleLink' => 'titleLink',
'redirector' => '',
'disablesessionprotection' => false,
'privateLinkByDefault' => false,
'config' => array(
'CONFIG_FILE' => 'tests/Updater/config.php',
'DATADIR' => 'tests/Updater',
'PAGECACHE' => 'sandbox/pagecache',
'config1' => 'config1data',
'config2' => 'config2data',
)
);
}
/**
* Executed after each test.
*
* @return void
*/
public function tearDown()
{
if (is_file(self::$configFields['config']['CONFIG_FILE'])) {
unlink(self::$configFields['config']['CONFIG_FILE']);
}
if (is_file(self::$configFields['config']['DATADIR'] . '/options.php')) {
unlink(self::$configFields['config']['DATADIR'] . '/options.php');
}
if (is_file(self::$configFields['config']['DATADIR'] . '/updates.json')) {
unlink(self::$configFields['config']['DATADIR'] . '/updates.json');
}
$this->conf = new ConfigManager(self::$configFile);
}
/**
@ -69,9 +38,10 @@ public function tearDown()
public function testReadEmptyUpdatesFile()
{
$this->assertEquals(array(), read_updates_file(''));
$updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json';
$updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
touch($updatesFile);
$this->assertEquals(array(), read_updates_file($updatesFile));
unlink($updatesFile);
}
/**
@ -79,7 +49,7 @@ public function testReadEmptyUpdatesFile()
*/
public function testReadWriteUpdatesFile()
{
$updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json';
$updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
$updatesMethods = array('m1', 'm2', 'm3');
write_updates_file($updatesFile, $updatesMethods);
@ -91,6 +61,7 @@ public function testReadWriteUpdatesFile()
write_updates_file($updatesFile, $updatesMethods);
$readMethods = read_updates_file($updatesFile);
$this->assertEquals($readMethods, $updatesMethods);
unlink($updatesFile);
}
/**
@ -112,10 +83,15 @@ public function testWriteEmptyUpdatesFile()
*/
public function testWriteUpdatesFileNotWritable()
{
$updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json';
$updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
touch($updatesFile);
chmod($updatesFile, 0444);
try {
@write_updates_file($updatesFile, array('test'));
} catch (Exception $e) {
unlink($updatesFile);
throw $e;
}
}
/**
@ -131,10 +107,10 @@ public function testNoUpdates()
'updateMethodDummy3',
'updateMethodException',
);
$updater = new DummyUpdater($updates, array(), array(), true);
$updater = new DummyUpdater($updates, array(), $this->conf, true);
$this->assertEquals(array(), $updater->update());
$updater = new DummyUpdater(array(), array(), array(), false);
$updater = new DummyUpdater(array(), array(), $this->conf, false);
$this->assertEquals(array(), $updater->update());
}
@ -149,7 +125,7 @@ public function testUpdatesFirstTime()
'updateMethodDummy2',
'updateMethodDummy3',
);
$updater = new DummyUpdater($updates, array(), array(), true);
$updater = new DummyUpdater($updates, array(), $this->conf, true);
$this->assertEquals($expectedUpdates, $updater->update());
}
@ -165,7 +141,7 @@ public function testOneUpdate()
);
$expectedUpdate = array('updateMethodDummy2');
$updater = new DummyUpdater($updates, array(), array(), true);
$updater = new DummyUpdater($updates, array(), $this->conf, true);
$this->assertEquals($expectedUpdate, $updater->update());
}
@ -182,7 +158,7 @@ public function testUpdateFailed()
'updateMethodDummy3',
);
$updater = new DummyUpdater($updates, array(), array(), true);
$updater = new DummyUpdater($updates, array(), $this->conf, true);
$updater->update();
}
@ -195,26 +171,28 @@ public function testUpdateFailed()
*/
public function testUpdateMergeDeprecatedConfig()
{
// init
writeConfig(self::$configFields, true);
$configCopy = self::$configFields;
$invert = !$configCopy['privateLinkByDefault'];
$configCopy['privateLinkByDefault'] = $invert;
$this->conf->setConfigFile('tests/utils/config/configPhp');
$this->conf->reset();
// Use writeConfig to create a options.php
$configCopy['config']['CONFIG_FILE'] = 'tests/Updater/options.php';
writeConfig($configCopy, true);
$optionsFile = 'tests/Updater/options.php';
$options = '<?php
$GLOBALS[\'privateLinkByDefault\'] = true;';
file_put_contents($optionsFile, $options);
$this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));
// tmp config file.
$this->conf->setConfigFile('tests/Updater/config');
// merge configs
$updater = new Updater(array(), self::$configFields, array(), true);
$updater = new Updater(array(), array(), $this->conf, true);
// This writes a new config file in tests/Updater/config.php
$updater->updateMethodMergeDeprecatedConfigFile();
// make sure updated field is changed
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);
$this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));
$this->conf->reload();
$this->assertTrue($this->conf->get('privacy.default_private_links'));
$this->assertFalse(is_file($optionsFile));
// Delete the generated file.
unlink($this->conf->getConfigFileExt());
}
/**
@ -222,23 +200,67 @@ public function testUpdateMergeDeprecatedConfig()
*/
public function testMergeDeprecatedConfigNoFile()
{
writeConfig(self::$configFields, true);
$updater = new Updater(array(), self::$configFields, array(), true);
$updater = new Updater(array(), array(), $this->conf, true);
$updater->updateMethodMergeDeprecatedConfigFile();
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
$this->assertEquals('root', $this->conf->get('credentials.login'));
}
/**
* Test renameDashTags update method.
*/
public function testRenameDashTags()
{
$refDB = new ReferenceLinkDB();
$refDB->write(self::$testDatastore);
$linkDB = new LinkDB(self::$testDatastore, true, false);
$this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
$updater = new Updater(array(), self::$configFields, $linkDB, true);
$updater = new Updater(array(), $linkDB, $this->conf, true);
$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';
$this->conf->setConfigFile($configFile);
$this->conf->reset();
// The ConfigIO is initialized with ConfigPhp.
$this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp);
$updater = new Updater(array(), array(), $this->conf, false);
$done = $updater->updateMethodConfigToJson();
$this->assertTrue($done);
// The ConfigIO has been updated to ConfigJson.
$this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson);
$this->assertTrue(file_exists($this->conf->getConfigFileExt()));
// Check JSON config data.
$this->conf->reload();
$this->assertEquals('root', $this->conf->get('credentials.login'));
$this->assertEquals('lala', $this->conf->get('redirector.url'));
$this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore'));
$this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION'));
rename($configFile . '.save.php', $configFile . '.php');
unlink($this->conf->getConfigFileExt());
}
/**
* Launch config conversion update with an existing JSON file => nothing to do.
*/
public function testConfigToJsonNothingToDo()
{
$filetime = filemtime($this->conf->getConfigFileExt());
$updater = new Updater(array(), array(), $this->conf, false);
$done = $updater->updateMethodConfigToJson();
$this->assertTrue($done);
$expected = filemtime($this->conf->getConfigFileExt());
$this->assertEquals($expected, $filetime);
}
}

View file

@ -0,0 +1,133 @@
<?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['credentials']['login']);
$this->assertEquals('lala', $conf['redirector']['url']);
$this->assertEquals('tests/utils/config/datastore.php', $conf['resource']['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(
'credentials' => array(
'login' => 'root',
),
'resource' => array(
'datastore' => 'data/datastore.php',
),
'redirector' => array(
'url' => 'lala',
),
'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 = '{
"credentials": {
"login": "root"
},
"resource": {
"datastore": "data\/datastore.php"
},
"redirector": {
"url": "lala"
},
"plugins": {
"WALLABAG_VERSION": "1"
}
}';
} else {
$expected = '{"credentials":{"login":"root"},"resource":{"datastore":"data\/datastore.php"},"redirector":{"url":"lala"},"plugins":{"WALLABAG_VERSION":"1"}}';
}
$expected = ConfigJson::getPhpHeaders() . $expected . ConfigJson::getPhpSuffix();
$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']['url'] = 'blabla';
$this->configIO->write($dest, $conf);
$conf = $this->configIO->read($dest);
$this->assertEquals('blabla', $conf['redirector']['url']);
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);
}
}

View file

@ -0,0 +1,172 @@
<?php
/**
* Unit tests for Class ConfigManagerTest
*
* 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
{
/**
* @var ConfigManager
*/
protected $conf;
public function setUp()
{
$this->conf = new ConfigManager('tests/utils/config/configJson');
}
/**
* Simple config test:
* 1. Set settings.
* 2. Check settings value.
*/
public function testSetGet()
{
$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->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->conf->setConfigFile('tests/utils/config/configTmp');
$this->conf->write(true);
$this->conf->reload();
unlink($this->conf->getConfigFileExt());
$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'));
}
/**
* Test set/write/get with nested keys.
*/
public function testSetWriteGetNested()
{
$this->conf->set('foo.bar.key.stuff', 'testSetWriteGetNested');
$this->conf->setConfigFile('tests/utils/config/configTmp');
$this->conf->write(true);
$this->conf->reload();
unlink($this->conf->getConfigFileExt());
$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()
{
$this->conf->setConfigFile('tests/utils/config/configTmp');
$this->assertFalse(file_exists($this->conf->getConfigFileExt()));
$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('credentials.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()
{
$confIO = $this->conf->getConfigIO();
$this->conf->reset();
$this->assertFalse($confIO === $this->conf->getConfigIO());
}
/**
* Reload the config from file.
*/
public function testReload()
{
$this->conf->setConfigFile('tests/utils/config/configTmp');
$newConf = ConfigJson::getPhpHeaders() . '{ "key": "value" }';
file_put_contents($this->conf->getConfigFileExt(), $newConf);
$this->conf->reload();
unlink($this->conf->getConfigFileExt());
// Previous conf no longer exists, and new values have been loaded.
$this->assertFalse($this->conf->exists('credentials.login'));
$this->assertEquals('value', $this->conf->get('key'));
}
}

View file

@ -0,0 +1,82 @@
<?php
require_once 'application/config/ConfigPhp.php';
/**
* Class ConfigPhpTest
*/
class ConfigPhpTest extends PHPUnit_Framework_TestCase
{
/**
* @var ConfigPhp
*/
protected $configIO;
public function setUp()
{
$this->configIO = new ConfigPhp();
}
/**
* Read a simple existing config file.
*/
public function testRead()
{
$conf = $this->configIO->read('tests/utils/config/configPhp.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'));
}
/**
* Write a new config file.
*/
public function testWriteNew()
{
$dataFile = 'tests/utils/config/configWrite.php';
$data = array(
'login' => 'root',
'redirector' => 'lala',
'config' => array(
'DATASTORE' => 'data/datastore.php',
),
'plugins' => array(
'WALLABAG_VERSION' => '1',
)
);
$this->configIO->write($dataFile, $data);
$expected = '<?php
$GLOBALS[\'login\'] = \'root\';
$GLOBALS[\'redirector\'] = \'lala\';
$GLOBALS[\'config\'][\'DATASTORE\'] = \'data/datastore.php\';
$GLOBALS[\'plugins\'][\'WALLABAG_VERSION\'] = \'1\';
';
$this->assertEquals($expected, file_get_contents($dataFile));
unlink($dataFile);
}
/**
* Overwrite an existing setting.
*/
public function testOverwrite()
{
$source = 'tests/utils/config/configPhp.php';
$dest = 'tests/utils/config/configOverwrite.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);
}
}

View file

@ -0,0 +1,121 @@
<?php
/**
* Config' tests
*/
require_once 'application/config/ConfigPlugin.php';
/**
* Unitary tests for Shaarli config related functions
*/
class ConfigPluginTest extends PHPUnit_Framework_TestCase
{
/**
* Test save_plugin_config with valid data.
*
* @throws PluginConfigOrderException
*/
public function testSavePluginConfigValid()
{
$data = array(
'order_plugin1' => 2, // no plugin related
'plugin2' => 0, // new - at the end
'plugin3' => 0, // 2nd
'order_plugin3' => 8,
'plugin4' => 0, // 1st
'order_plugin4' => 5,
);
$expected = array(
'plugin3',
'plugin4',
'plugin2',
);
$out = save_plugin_config($data);
$this->assertEquals($expected, $out);
}
/**
* Test save_plugin_config with invalid data.
*
* @expectedException PluginConfigOrderException
*/
public function testSavePluginConfigInvalid()
{
$data = array(
'plugin2' => 0,
'plugin3' => 0,
'order_plugin3' => 0,
'plugin4' => 0,
'order_plugin4' => 0,
);
save_plugin_config($data);
}
/**
* Test save_plugin_config without data.
*/
public function testSavePluginConfigEmpty()
{
$this->assertEquals(array(), save_plugin_config(array()));
}
/**
* Test validate_plugin_order with valid data.
*/
public function testValidatePluginOrderValid()
{
$data = array(
'order_plugin1' => 2,
'plugin2' => 0,
'plugin3' => 0,
'order_plugin3' => 1,
'plugin4' => 0,
'order_plugin4' => 5,
);
$this->assertTrue(validate_plugin_order($data));
}
/**
* Test validate_plugin_order with invalid data.
*/
public function testValidatePluginOrderInvalid()
{
$data = array(
'order_plugin1' => 2,
'order_plugin3' => 1,
'order_plugin4' => 1,
);
$this->assertFalse(validate_plugin_order($data));
}
/**
* Test load_plugin_parameter_values.
*/
public function testLoadPluginParameterValues()
{
$plugins = array(
'plugin_name' => array(
'parameters' => array(
'param1' => true,
'param2' => false,
'param3' => '',
)
)
);
$parameters = array(
'param1' => 'value1',
'param2' => 'value2',
);
$result = load_plugin_parameter_values($plugins, $parameters);
$this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
$this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
$this->assertEquals('', $result['plugin_name']['parameters']['param3']);
}
}

View file

@ -4,6 +4,8 @@
* PluginReadityourselfTest.php.php
*/
// FIXME! add an init method.
$conf = new ConfigManager('');
require_once 'plugins/readityourself/readityourself.php';
/**
@ -25,7 +27,8 @@ function setUp()
*/
function testReadityourselfLinklist()
{
$GLOBALS['plugins']['READITYOUSELF_URL'] = 'value';
$conf = new ConfigManager('');
$conf->set('plugins.READITYOUSELF_URL', 'value');
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
@ -36,7 +39,7 @@ function testReadityourselfLinklist()
)
);
$data = hook_readityourself_render_linklist($data);
$data = hook_readityourself_render_linklist($data, $conf);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
@ -52,7 +55,8 @@ function testReadityourselfLinklist()
*/
function testReadityourselfLinklistWithoutConfig()
{
unset($GLOBALS['plugins']['READITYOUSELF_URL']);
$conf = new ConfigManager('');
$conf->set('plugins.READITYOUSELF_URL', null);
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
@ -63,7 +67,7 @@ function testReadityourselfLinklistWithoutConfig()
)
);
$data = hook_readityourself_render_linklist($data);
$data = hook_readityourself_render_linklist($data, $conf);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);

View file

@ -4,6 +4,8 @@
* PluginWallabagTest.php.php
*/
// FIXME! add an init method.
$conf = new ConfigManager('');
require_once 'plugins/wallabag/wallabag.php';
/**
@ -25,7 +27,8 @@ function setUp()
*/
function testWallabagLinklist()
{
$GLOBALS['plugins']['WALLABAG_URL'] = 'value';
$conf = new ConfigManager('');
$conf->set('plugins.WALLABAG_URL', 'value');
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
@ -36,7 +39,7 @@ function testWallabagLinklist()
)
);
$data = hook_wallabag_render_linklist($data);
$data = hook_wallabag_render_linklist($data, $conf);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
@ -45,7 +48,6 @@ function testWallabagLinklist()
// plugin data
$this->assertEquals(1, count($link['link_plugin']));
$this->assertNotFalse(strpos($link['link_plugin'][0], urlencode($str)));
$this->assertNotFalse(strpos($link['link_plugin'][0], $GLOBALS['plugins']['WALLABAG_URL']));
$this->assertNotFalse(strpos($link['link_plugin'][0], $conf->get('plugins.WALLABAG_URL')));
}
}

View file

@ -0,0 +1,5 @@
<?php /*
{
bad: bad,
}
*/ ?>

View file

@ -0,0 +1,34 @@
<?php /*
{
"credentials": {
"login":"root",
"hash":"hash",
"salt":"salt"
},
"security": {
"session_protection_disabled":false
},
"general": {
"timezone":"Europe\/Paris",
"title": "Shaarli",
"header_link": "?"
},
"privacy": {
"default_private_links":true
},
"redirector": {
"url":"lala"
},
"config": {
"foo": "bar"
},
"resource": {
"datastore": "tests\/utils\/config\/datastore.php",
"data_dir": "tests\/utils\/config"
},
"plugins": {
"WALLABAG_VERSION": 1
}
}
*/ ?>

View file

@ -0,0 +1,14 @@
<?php
$GLOBALS['login'] = 'root';
$GLOBALS['hash'] = 'hash';
$GLOBALS['salt'] = 'salt';
$GLOBALS['timezone'] = 'Europe/Paris';
$GLOBALS['title'] = 'title';
$GLOBALS['titleLink'] = 'titleLink';
$GLOBALS['redirector'] = 'lala';
$GLOBALS['disablesessionprotection'] = false;
$GLOBALS['privateLinkByDefault'] = false;
$GLOBALS['config']['DATADIR'] = 'tests/Updater';
$GLOBALS['config']['PAGECACHE'] = 'sandbox/pagecache';
$GLOBALS['config']['DATASTORE'] = 'data/datastore.php';
$GLOBALS['plugins']['WALLABAG_VERSION'] = '1';

View file

@ -9,40 +9,82 @@
<input type="hidden" name="token" value="{$token}">
<table id="configuration_table">
<tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr>
<tr>
<td><b>Page title:</b></td>
<td><input type="text" name="title" id="title" size="50" value="{$title}"></td>
</tr>
<tr><td><b>Title link:</b></td><td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label for="titleLink">(default value is: ?)</label></td></tr>
<tr><td><b>Timezone:</b></td><td>{$timezone_form}</td></tr>
<tr>
<td><b>Title link:</b></td>
<td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label
for="titleLink">(default value is: ?)</label></td>
</tr>
<tr>
<td><b>Timezone:</b></td>
<td>{$timezone_form}</td>
</tr>
<tr><td><b>Redirector</b></td><td><input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>(e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)</td></tr>
<tr>
<td><b>Redirector</b></td>
<td>
<input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>
(e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)
</td>
</tr>
<tr><td><b>Security:</b></td><td><input type="checkbox" name="disablesessionprotection" id="disablesessionprotection" {if="!empty($GLOBALS['disablesessionprotection'])"}checked{/if}><label for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get disconnected often or if your IP address changes often.)</label></td></tr>
<tr>
<td><b>Security:</b></td>
<td>
<input type="checkbox" name="disablesessionprotection" id="disablesessionprotection"
{if="$private_links_default"}checked{/if}>
<label
for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get
disconnected often or if your IP address changes often.)</label>
</td>
</tr>
<tr><td valign="top"><b>New link:</b></td><td>
<input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault">&nbsp;All new links are private by default</label></td>
<tr>
<td valign="top"><b>New link:</b></td>
<td>
<input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault"
{if="$private_links_default"}checked{/if}/>
<label for="privateLinkByDefault">
&nbsp;All new links are private by default
</label>
</td>
</tr>
<tr>
<td valign="top"><b>RSS direct links</b></td>
<td>
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/>
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks"
{if="$enable_rss_permalinks"}checked{/if}/>
<label for="enableRssPermalinks">
&nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}.</b>
&nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>
{if="$enable_rss_permalinks"}enabled{else}disabled{/if}.</b>
</label>
</td>
</tr>
<tr>
<td valign="top"><b>Hide public links</b></td>
<td>
<input type="checkbox" name="hidePublicLinks" id="hidePublicLinks" {if="!empty($GLOBALS['config']['HIDE_PUBLIC_LINKS'])"}checked{/if}/><label for="hidePublicLinks">&nbsp;
Do not show any links if the user is not logged in.</label>
<input type="checkbox" name="hidePublicLinks" id="hidePublicLinks"
{if="$hide_public_links"}checked{/if}/>
<label for="hidePublicLinks">&nbsp;Do not show any links if the user is not logged in.</label>
</td>
</tr>
<tr><td valign="top"><b>Update:</b></td><td>
<input type="checkbox" name="updateCheck" id="updateCheck" {if="!empty($GLOBALS['config']['ENABLE_UPDATECHECK'])"}checked{/if}/>
<label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td>
<tr>
<td valign="top"><b>Update:</b></td>
<td>
<input type="checkbox" name="updateCheck" id="updateCheck"
{if="$enable_update_check"}checked{/if}/>
<label for="updateCheck">&nbsp;Notify me when a new release is ready</label>
</td>
</tr>
<tr><td></td><td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
<tr>
<td></td>
<td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td>
</tr>
</table>
</form>
</div>

View file

@ -53,7 +53,7 @@
<img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
</a>
</div>
{if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"}
{if="!$hide_timestamps || isLoggedIn()"}
<div class="dailyEntryLinkdate">
<a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a>
</div>

View file

@ -6,7 +6,7 @@
<description><![CDATA[
{loop="links"}
<h3><a href="{$value.url}">{$value.title}</a></h3>
<small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
<small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
{$value.url}</small><br>
{if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
{if="$value.description"}{$value.formatedDescription}{/if}

View file

@ -25,7 +25,7 @@
{$value}
{/loop}
{if="($link_is_new && $GLOBALS['privateLinkByDefault']==true) || $link.private == true"}
{if="($link_is_new && $default_private_links) || $link.private == true"}
<input type="checkbox" checked="checked" name="lf_private" id="lf_private">
&nbsp;<label for="lf_private"><i>Private</i></label><br>
{else}
@ -43,12 +43,10 @@
{if="$source !== 'firefoxsocialapi'"}
{include="page.footer"}
{/if}
{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"}
<script src="inc/awesomplete.min.js#"></script>
<script src="inc/awesomplete-multiple-tags.js#"></script>
<script>
awesompleteUniqueTag('#lf_tags');
</script>
{/if}
</body>
</html>

View 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>

View file

@ -21,14 +21,14 @@
<li><a href="?do=logout">Logout</a></li>
<li><a href="?do=tools">Tools</a></li>
<li><a href="?do=addlink">Add link</a></li>
{elseif="$GLOBALS['config']['OPEN_SHAARLI']"}
{elseif="$openshaarli"}
<li><a href="?do=tools">Tools</a></li>
<li><a href="?do=addlink">Add link</a></li>
{else}
<li><a href="?do=login">Login</a></li>
{/if}
<li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li>
{if="$GLOBALS['config']['SHOW_ATOM']"}
{if="$showatom"}
<li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li>
{/if}
<li><a href="?do=tagcloud">Tag cloud</a></li>

View 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>