aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthur <arthur@hoa.ro>2016-07-09 07:19:48 +0200
committerGitHub <noreply@github.com>2016-07-09 07:19:48 +0200
commit649af5b501d2a90448242f53764ff693e9854039 (patch)
tree23cde80a7ee2949e552c48939ae22fa462cfa0fc
parenta9cfa38df92bd2e1e2c00a67b6ac1516a2116ade (diff)
parent5ff23f02b80ec6ddee28dee869171ee8e3656b7c (diff)
downloadShaarli-649af5b501d2a90448242f53764ff693e9854039.tar.gz
Shaarli-649af5b501d2a90448242f53764ff693e9854039.tar.zst
Shaarli-649af5b501d2a90448242f53764ff693e9854039.zip
Merge pull request #570 from ArthurHoaro/config-manager
Introduce a configuration manager
-rw-r--r--application/ApplicationUtils.php24
-rw-r--r--application/Config.php221
-rw-r--r--application/FileUtils.php8
-rw-r--r--application/PageBuilder.php46
-rw-r--r--application/PluginManager.php50
-rw-r--r--application/Updater.php110
-rw-r--r--application/Utils.php2
-rw-r--r--application/config/ConfigIO.php33
-rw-r--r--application/config/ConfigJson.php78
-rw-r--r--application/config/ConfigManager.php392
-rw-r--r--application/config/ConfigPhp.php132
-rw-r--r--application/config/ConfigPlugin.php120
-rw-r--r--index.php703
-rw-r--r--plugins/readityourself/config.php.dist3
-rw-r--r--plugins/readityourself/readityourself.php21
-rw-r--r--plugins/wallabag/README.md29
-rw-r--r--plugins/wallabag/config.php.dist4
-rw-r--r--plugins/wallabag/wallabag.php25
-rw-r--r--tests/ApplicationUtilsTest.php54
-rw-r--r--tests/ConfigTest.php244
-rw-r--r--tests/FeedBuilderTest.php6
-rw-r--r--tests/LinkDBTest.php2
-rw-r--r--tests/PluginManagerTest.php34
-rw-r--r--tests/Updater/DummyUpdater.php12
-rw-r--r--tests/Updater/UpdaterTest.php160
-rw-r--r--tests/config/ConfigJsonTest.php133
-rw-r--r--tests/config/ConfigManagerTest.php172
-rw-r--r--tests/config/ConfigPhpTest.php82
-rw-r--r--tests/config/ConfigPluginTest.php121
-rw-r--r--tests/plugins/PluginReadityourselfTest.php12
-rw-r--r--tests/plugins/PluginWallabagTest.php10
-rw-r--r--tests/utils/config/configInvalid.json.php5
-rw-r--r--tests/utils/config/configJson.json.php34
-rw-r--r--tests/utils/config/configPhp.php14
-rw-r--r--tpl/configure.html114
-rw-r--r--tpl/daily.html2
-rw-r--r--tpl/dailyrss.html2
-rw-r--r--tpl/editlink.html4
-rw-r--r--tpl/linklist.html2
-rw-r--r--tpl/page.header.html4
-rw-r--r--tpl/tools.html2
41 files changed, 2096 insertions, 1130 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 978fc9da..e67b2902 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -132,11 +132,11 @@ class ApplicationUtils
132 /** 132 /**
133 * Checks Shaarli has the proper access permissions to its resources 133 * Checks Shaarli has the proper access permissions to its resources
134 * 134 *
135 * @param array $globalConfig The $GLOBALS['config'] array 135 * @param ConfigManager $conf Configuration Manager instance.
136 * 136 *
137 * @return array A list of the detected configuration issues 137 * @return array A list of the detected configuration issues
138 */ 138 */
139 public static function checkResourcePermissions($globalConfig) 139 public static function checkResourcePermissions($conf)
140 { 140 {
141 $errors = array(); 141 $errors = array();
142 142
@@ -145,7 +145,7 @@ class ApplicationUtils
145 'application', 145 'application',
146 'inc', 146 'inc',
147 'plugins', 147 'plugins',
148 $globalConfig['RAINTPL_TPL'] 148 $conf->get('resource.raintpl_tpl'),
149 ) as $path) { 149 ) as $path) {
150 if (! is_readable(realpath($path))) { 150 if (! is_readable(realpath($path))) {
151 $errors[] = '"'.$path.'" directory is not readable'; 151 $errors[] = '"'.$path.'" directory is not readable';
@@ -154,10 +154,10 @@ class ApplicationUtils
154 154
155 // Check cache and data directories are readable and writeable 155 // Check cache and data directories are readable and writeable
156 foreach (array( 156 foreach (array(
157 $globalConfig['CACHEDIR'], 157 $conf->get('resource.thumbnails_cache'),
158 $globalConfig['DATADIR'], 158 $conf->get('resource.data_dir'),
159 $globalConfig['PAGECACHE'], 159 $conf->get('resource.page_cache'),
160 $globalConfig['RAINTPL_TMP'] 160 $conf->get('resource.raintpl_tmp'),
161 ) as $path) { 161 ) as $path) {
162 if (! is_readable(realpath($path))) { 162 if (! is_readable(realpath($path))) {
163 $errors[] = '"'.$path.'" directory is not readable'; 163 $errors[] = '"'.$path.'" directory is not readable';
@@ -169,11 +169,11 @@ class ApplicationUtils
169 169
170 // Check configuration files are readable and writeable 170 // Check configuration files are readable and writeable
171 foreach (array( 171 foreach (array(
172 $globalConfig['CONFIG_FILE'], 172 $conf->getConfigFileExt(),
173 $globalConfig['DATASTORE'], 173 $conf->get('resource.datastore'),
174 $globalConfig['IPBANS_FILENAME'], 174 $conf->get('resource.ban_file'),
175 $globalConfig['LOG_FILE'], 175 $conf->get('resource.log'),
176 $globalConfig['UPDATECHECK_FILENAME'] 176 $conf->get('resource.update_check'),
177 ) as $path) { 177 ) as $path) {
178 if (! is_file(realpath($path))) { 178 if (! is_file(realpath($path))) {
179 # the file may not exist yet 179 # the file may not exist yet
diff --git a/application/Config.php b/application/Config.php
deleted file mode 100644
index 05a59452..00000000
--- a/application/Config.php
+++ /dev/null
@@ -1,221 +0,0 @@
1<?php
2/**
3 * Functions related to configuration management.
4 */
5
6/**
7 * Re-write configuration file according to given array.
8 * Requires mandatory fields listed in $MANDATORY_FIELDS.
9 *
10 * @param array $config contains all configuration fields.
11 * @param bool $isLoggedIn true if user is logged in.
12 *
13 * @return void
14 *
15 * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
16 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
17 * @throws Exception: an error occured while writing the new config file.
18 */
19function writeConfig($config, $isLoggedIn)
20{
21 // These fields are required in configuration.
22 $MANDATORY_FIELDS = array(
23 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
24 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
25 );
26
27 if (!isset($config['config']['CONFIG_FILE'])) {
28 throw new MissingFieldConfigException('CONFIG_FILE');
29 }
30
31 // Only logged in user can alter config.
32 if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
33 throw new UnauthorizedConfigException();
34 }
35
36 // Check that all mandatory fields are provided in $config.
37 foreach ($MANDATORY_FIELDS as $field) {
38 if (!isset($config[$field])) {
39 throw new MissingFieldConfigException($field);
40 }
41 }
42
43 $configStr = '<?php '. PHP_EOL;
44 $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
45 $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
46 $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
47 $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
48 $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
49 $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
50 $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
51 $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
52 $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
53 $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
54
55 // Store all $config['config']
56 foreach ($config['config'] as $key => $value) {
57 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
58 }
59
60 if (isset($config['plugins'])) {
61 foreach ($config['plugins'] as $key => $value) {
62 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
63 }
64 }
65
66 if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
67 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
68 ) {
69 throw new Exception(
70 'Shaarli could not create the config file.
71 Please make sure Shaarli has the right to write in the folder is it installed in.'
72 );
73 }
74}
75
76/**
77 * Process plugin administration form data and save it in an array.
78 *
79 * @param array $formData Data sent by the plugin admin form.
80 *
81 * @return array New list of enabled plugin, ordered.
82 *
83 * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
84 */
85function save_plugin_config($formData)
86{
87 // Make sure there are no duplicates in orders.
88 if (!validate_plugin_order($formData)) {
89 throw new PluginConfigOrderException();
90 }
91
92 $plugins = array();
93 $newEnabledPlugins = array();
94 foreach ($formData as $key => $data) {
95 if (startsWith($key, 'order')) {
96 continue;
97 }
98
99 // If there is no order, it means a disabled plugin has been enabled.
100 if (isset($formData['order_' . $key])) {
101 $plugins[(int) $formData['order_' . $key]] = $key;
102 }
103 else {
104 $newEnabledPlugins[] = $key;
105 }
106 }
107
108 // New enabled plugins will be added at the end of order.
109 $plugins = array_merge($plugins, $newEnabledPlugins);
110
111 // Sort plugins by order.
112 if (!ksort($plugins)) {
113 throw new PluginConfigOrderException();
114 }
115
116 $finalPlugins = array();
117 // Make plugins order continuous.
118 foreach ($plugins as $plugin) {
119 $finalPlugins[] = $plugin;
120 }
121
122 return $finalPlugins;
123}
124
125/**
126 * Validate plugin array submitted.
127 * Will fail if there is duplicate orders value.
128 *
129 * @param array $formData Data from submitted form.
130 *
131 * @return bool true if ok, false otherwise.
132 */
133function validate_plugin_order($formData)
134{
135 $orders = array();
136 foreach ($formData as $key => $value) {
137 // No duplicate order allowed.
138 if (in_array($value, $orders)) {
139 return false;
140 }
141
142 if (startsWith($key, 'order')) {
143 $orders[] = $value;
144 }
145 }
146
147 return true;
148}
149
150/**
151 * Affect plugin parameters values into plugins array.
152 *
153 * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
154 * @param mixed $config Plugins configuration.
155 *
156 * @return mixed Updated $plugins array.
157 */
158function load_plugin_parameter_values($plugins, $config)
159{
160 $out = $plugins;
161 foreach ($plugins as $name => $plugin) {
162 if (empty($plugin['parameters'])) {
163 continue;
164 }
165
166 foreach ($plugin['parameters'] as $key => $param) {
167 if (!empty($config[$key])) {
168 $out[$name]['parameters'][$key] = $config[$key];
169 }
170 }
171 }
172
173 return $out;
174}
175
176/**
177 * Exception used if a mandatory field is missing in given configuration.
178 */
179class MissingFieldConfigException extends Exception
180{
181 public $field;
182
183 /**
184 * Construct exception.
185 *
186 * @param string $field field name missing.
187 */
188 public function __construct($field)
189 {
190 $this->field = $field;
191 $this->message = 'Configuration value is required for '. $this->field;
192 }
193}
194
195/**
196 * Exception used if an unauthorized attempt to edit configuration has been made.
197 */
198class UnauthorizedConfigException extends Exception
199{
200 /**
201 * Construct exception.
202 */
203 public function __construct()
204 {
205 $this->message = 'You are not authorized to alter config.';
206 }
207}
208
209/**
210 * Exception used if an error occur while saving plugin configuration.
211 */
212class PluginConfigOrderException extends Exception
213{
214 /**
215 * Construct exception.
216 */
217 public function __construct()
218 {
219 $this->message = 'An error occurred while trying to save plugins loading order.';
220 }
221}
diff --git a/application/FileUtils.php b/application/FileUtils.php
index 6a12ef0e..6cac9825 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -9,11 +9,13 @@ class IOException extends Exception
9 /** 9 /**
10 * Construct a new IOException 10 * Construct a new IOException
11 * 11 *
12 * @param string $path path to the ressource that cannot be accessed 12 * @param string $path path to the resource that cannot be accessed
13 * @param string $message Custom exception message.
13 */ 14 */
14 public function __construct($path) 15 public function __construct($path, $message = '')
15 { 16 {
16 $this->path = $path; 17 $this->path = $path;
17 $this->message = 'Error accessing '.$this->path; 18 $this->message = empty($message) ? 'Error accessing' : $message;
19 $this->message .= PHP_EOL . $this->path;
18 } 20 }
19} 21}
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 82580787..7cd88370 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -15,12 +15,20 @@ class PageBuilder
15 private $tpl; 15 private $tpl;
16 16
17 /** 17 /**
18 * @var ConfigManager $conf Configuration Manager instance.
19 */
20 protected $conf;
21
22 /**
18 * PageBuilder constructor. 23 * PageBuilder constructor.
19 * $tpl is initialized at false for lazy loading. 24 * $tpl is initialized at false for lazy loading.
25 *
26 * @param ConfigManager $conf Configuration Manager instance (reference).
20 */ 27 */
21 function __construct() 28 function __construct(&$conf)
22 { 29 {
23 $this->tpl = false; 30 $this->tpl = false;
31 $this->conf = $conf;
24 } 32 }
25 33
26 /** 34 /**
@@ -33,17 +41,17 @@ class PageBuilder
33 try { 41 try {
34 $version = ApplicationUtils::checkUpdate( 42 $version = ApplicationUtils::checkUpdate(
35 shaarli_version, 43 shaarli_version,
36 $GLOBALS['config']['UPDATECHECK_FILENAME'], 44 $this->conf->get('resource.update_check'),
37 $GLOBALS['config']['UPDATECHECK_INTERVAL'], 45 $this->conf->get('updates.check_updates_interval'),
38 $GLOBALS['config']['ENABLE_UPDATECHECK'], 46 $this->conf->get('updates.check_updates'),
39 isLoggedIn(), 47 isLoggedIn(),
40 $GLOBALS['config']['UPDATECHECK_BRANCH'] 48 $this->conf->get('updates.check_updates_branch')
41 ); 49 );
42 $this->tpl->assign('newVersion', escape($version)); 50 $this->tpl->assign('newVersion', escape($version));
43 $this->tpl->assign('versionError', ''); 51 $this->tpl->assign('versionError', '');
44 52
45 } catch (Exception $exc) { 53 } catch (Exception $exc) {
46 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage()); 54 logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
47 $this->tpl->assign('newVersion', ''); 55 $this->tpl->assign('newVersion', '');
48 $this->tpl->assign('versionError', escape($exc->getMessage())); 56 $this->tpl->assign('versionError', escape($exc->getMessage()));
49 } 57 }
@@ -62,19 +70,24 @@ class PageBuilder
62 $this->tpl->assign('scripturl', index_url($_SERVER)); 70 $this->tpl->assign('scripturl', index_url($_SERVER));
63 $this->tpl->assign('pagetitle', 'Shaarli'); 71 $this->tpl->assign('pagetitle', 'Shaarli');
64 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links? 72 $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
65 if (!empty($GLOBALS['title'])) { 73 if ($this->conf->exists('general.title')) {
66 $this->tpl->assign('pagetitle', $GLOBALS['title']); 74 $this->tpl->assign('pagetitle', $this->conf->get('general.title'));
67 } 75 }
68 if (!empty($GLOBALS['titleLink'])) { 76 if ($this->conf->exists('general.header_link')) {
69 $this->tpl->assign('titleLink', $GLOBALS['titleLink']); 77 $this->tpl->assign('titleLink', $this->conf->get('general.header_link'));
70 } 78 }
71 if (!empty($GLOBALS['pagetitle'])) { 79 if ($this->conf->exists('pagetitle')) {
72 $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']); 80 $this->tpl->assign('pagetitle', $this->conf->get('pagetitle'));
73 } 81 }
74 $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']); 82 $this->tpl->assign('shaarlititle', $this->conf->get('title', 'Shaarli'));
83 $this->tpl->assign('openshaarli', $this->conf->get('security.open_shaarli', false));
84 $this->tpl->assign('showatom', $this->conf->get('feed.show_atom', false));
85 $this->tpl->assign('hide_timestamps', $this->conf->get('privacy.hide_timestamps', false));
75 if (!empty($GLOBALS['plugin_errors'])) { 86 if (!empty($GLOBALS['plugin_errors'])) {
76 $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']); 87 $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']);
77 } 88 }
89 // To be removed with a proper theme configuration.
90 $this->tpl->assign('conf', $this->conf);
78 } 91 }
79 92
80 /** 93 /**
@@ -85,7 +98,6 @@ class PageBuilder
85 */ 98 */
86 public function assign($placeholder, $value) 99 public function assign($placeholder, $value)
87 { 100 {
88 // Lazy initialization
89 if ($this->tpl === false) { 101 if ($this->tpl === false) {
90 $this->initialize(); 102 $this->initialize();
91 } 103 }
@@ -101,7 +113,6 @@ class PageBuilder
101 */ 113 */
102 public function assignAll($data) 114 public function assignAll($data)
103 { 115 {
104 // Lazy initialization
105 if ($this->tpl === false) { 116 if ($this->tpl === false) {
106 $this->initialize(); 117 $this->initialize();
107 } 118 }
@@ -113,6 +124,7 @@ class PageBuilder
113 foreach ($data as $key => $value) { 124 foreach ($data as $key => $value) {
114 $this->assign($key, $value); 125 $this->assign($key, $value);
115 } 126 }
127 return true;
116 } 128 }
117 129
118 /** 130 /**
@@ -123,10 +135,10 @@ class PageBuilder
123 */ 135 */
124 public function renderPage($page) 136 public function renderPage($page)
125 { 137 {
126 // Lazy initialization 138 if ($this->tpl === false) {
127 if ($this->tpl===false) {
128 $this->initialize(); 139 $this->initialize();
129 } 140 }
141
130 $this->tpl->draw($page); 142 $this->tpl->draw($page);
131 } 143 }
132 144
diff --git a/application/PluginManager.php b/application/PluginManager.php
index 787ac6a9..dca7e63e 100644
--- a/application/PluginManager.php
+++ b/application/PluginManager.php
@@ -4,18 +4,10 @@
4 * Class PluginManager 4 * Class PluginManager
5 * 5 *
6 * Use to manage, load and execute plugins. 6 * Use to manage, load and execute plugins.
7 *
8 * Using Singleton design pattern.
9 */ 7 */
10class PluginManager 8class PluginManager
11{ 9{
12 /** 10 /**
13 * PluginManager singleton instance.
14 * @var PluginManager $instance
15 */
16 private static $instance;
17
18 /**
19 * List of authorized plugins from configuration file. 11 * List of authorized plugins from configuration file.
20 * @var array $authorizedPlugins 12 * @var array $authorizedPlugins
21 */ 13 */
@@ -28,6 +20,11 @@ class PluginManager
28 private $loadedPlugins = array(); 20 private $loadedPlugins = array();
29 21
30 /** 22 /**
23 * @var ConfigManager Configuration Manager instance.
24 */
25 protected $conf;
26
27 /**
31 * Plugins subdirectory. 28 * Plugins subdirectory.
32 * @var string $PLUGINS_PATH 29 * @var string $PLUGINS_PATH
33 */ 30 */
@@ -40,33 +37,13 @@ class PluginManager
40 public static $META_EXT = 'meta'; 37 public static $META_EXT = 'meta';
41 38
42 /** 39 /**
43 * Private constructor: new instances not allowed. 40 * Constructor.
44 */
45 private function __construct()
46 {
47 }
48
49 /**
50 * Cloning isn't allowed either.
51 *
52 * @return void
53 */
54 private function __clone()
55 {
56 }
57
58 /**
59 * Return existing instance of PluginManager, or create it.
60 * 41 *
61 * @return PluginManager instance. 42 * @param ConfigManager $conf Configuration Manager instance.
62 */ 43 */
63 public static function getInstance() 44 public function __construct(&$conf)
64 { 45 {
65 if (!(self::$instance instanceof self)) { 46 $this->conf = $conf;
66 self::$instance = new self();
67 }
68
69 return self::$instance;
70 } 47 }
71 48
72 /** 49 /**
@@ -102,9 +79,9 @@ class PluginManager
102 /** 79 /**
103 * Execute all plugins registered hook. 80 * Execute all plugins registered hook.
104 * 81 *
105 * @param string $hook name of the hook to trigger. 82 * @param string $hook name of the hook to trigger.
106 * @param array $data list of data to manipulate passed by reference. 83 * @param array $data list of data to manipulate passed by reference.
107 * @param array $params additional parameters such as page target. 84 * @param array $params additional parameters such as page target.
108 * 85 *
109 * @return void 86 * @return void
110 */ 87 */
@@ -122,7 +99,7 @@ class PluginManager
122 $hookFunction = $this->buildHookName($hook, $plugin); 99 $hookFunction = $this->buildHookName($hook, $plugin);
123 100
124 if (function_exists($hookFunction)) { 101 if (function_exists($hookFunction)) {
125 $data = call_user_func($hookFunction, $data); 102 $data = call_user_func($hookFunction, $data, $this->conf);
126 } 103 }
127 } 104 }
128 } 105 }
@@ -148,6 +125,7 @@ class PluginManager
148 throw new PluginFileNotFoundException($pluginName); 125 throw new PluginFileNotFoundException($pluginName);
149 } 126 }
150 127
128 $conf = $this->conf;
151 include_once $pluginFilePath; 129 include_once $pluginFilePath;
152 130
153 $this->loadedPlugins[] = $pluginName; 131 $this->loadedPlugins[] = $pluginName;
diff --git a/application/Updater.php b/application/Updater.php
index 58c13c07..fd45d17f 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -13,14 +13,14 @@ class Updater
13 protected $doneUpdates; 13 protected $doneUpdates;
14 14
15 /** 15 /**
16 * @var array Shaarli's configuration array. 16 * @var LinkDB instance.
17 */ 17 */
18 protected $config; 18 protected $linkDB;
19 19
20 /** 20 /**
21 * @var LinkDB instance. 21 * @var ConfigManager $conf Configuration Manager instance.
22 */ 22 */
23 protected $linkDB; 23 protected $conf;
24 24
25 /** 25 /**
26 * @var bool True if the user is logged in, false otherwise. 26 * @var bool True if the user is logged in, false otherwise.
@@ -35,16 +35,16 @@ class Updater
35 /** 35 /**
36 * Object constructor. 36 * Object constructor.
37 * 37 *
38 * @param array $doneUpdates Updates which are already done. 38 * @param array $doneUpdates Updates which are already done.
39 * @param array $config Shaarli's configuration array. 39 * @param LinkDB $linkDB LinkDB instance.
40 * @param LinkDB $linkDB LinkDB instance. 40 * @oaram ConfigManager $conf Configuration Manager instance.
41 * @param boolean $isLoggedIn True if the user is logged in. 41 * @param boolean $isLoggedIn True if the user is logged in.
42 */ 42 */
43 public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn) 43 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
44 { 44 {
45 $this->doneUpdates = $doneUpdates; 45 $this->doneUpdates = $doneUpdates;
46 $this->config = $config;
47 $this->linkDB = $linkDB; 46 $this->linkDB = $linkDB;
47 $this->conf = $conf;
48 $this->isLoggedIn = $isLoggedIn; 48 $this->isLoggedIn = $isLoggedIn;
49 49
50 // Retrieve all update methods. 50 // Retrieve all update methods.
@@ -114,19 +114,19 @@ class Updater
114 */ 114 */
115 public function updateMethodMergeDeprecatedConfigFile() 115 public function updateMethodMergeDeprecatedConfigFile()
116 { 116 {
117 $config_file = $this->config['config']['CONFIG_FILE']; 117 if (is_file($this->conf->get('resource.data_dir') . '/options.php')) {
118 118 include $this->conf->get('resource.data_dir') . '/options.php';
119 if (is_file($this->config['config']['DATADIR'].'/options.php')) {
120 include $this->config['config']['DATADIR'].'/options.php';
121 119
122 // Load GLOBALS into config 120 // Load GLOBALS into config
121 $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
122 $allowedKeys[] = 'config';
123 foreach ($GLOBALS as $key => $value) { 123 foreach ($GLOBALS as $key => $value) {
124 $this->config[$key] = $value; 124 if (in_array($key, $allowedKeys)) {
125 $this->conf->set($key, $value);
126 }
125 } 127 }
126 $this->config['config']['CONFIG_FILE'] = $config_file; 128 $this->conf->write($this->isLoggedIn);
127 writeConfig($this->config, $this->isLoggedIn); 129 unlink($this->conf->get('resource.data_dir').'/options.php');
128
129 unlink($this->config['config']['DATADIR'].'/options.php');
130 } 130 }
131 131
132 return true; 132 return true;
@@ -143,7 +143,76 @@ class Updater
143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); 143 $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
144 $this->linkDB[$link['linkdate']] = $link; 144 $this->linkDB[$link['linkdate']] = $link;
145 } 145 }
146 $this->linkDB->savedb($this->config['config']['PAGECACHE']); 146 $this->linkDB->savedb($this->conf->get('resource.page_cache'));
147 return true;
148 }
149
150 /**
151 * Move old configuration in PHP to the new config system in JSON format.
152 *
153 * Will rename 'config.php' into 'config.save.php' and create 'config.json.php'.
154 * It will also convert legacy setting keys to the new ones.
155 */
156 public function updateMethodConfigToJson()
157 {
158 // JSON config already exists, nothing to do.
159 if ($this->conf->getConfigIO() instanceof ConfigJson) {
160 return true;
161 }
162
163 $configPhp = new ConfigPhp();
164 $configJson = new ConfigJson();
165 $oldConfig = $configPhp->read($this->conf->getConfigFile() . '.php');
166 rename($this->conf->getConfigFileExt(), $this->conf->getConfigFile() . '.save.php');
167 $this->conf->setConfigIO($configJson);
168 $this->conf->reload();
169
170 $legacyMap = array_flip(ConfigPhp::$LEGACY_KEYS_MAPPING);
171 foreach (ConfigPhp::$ROOT_KEYS as $key) {
172 $this->conf->set($legacyMap[$key], $oldConfig[$key]);
173 }
174
175 // Set sub config keys (config and plugins)
176 $subConfig = array('config', 'plugins');
177 foreach ($subConfig as $sub) {
178 foreach ($oldConfig[$sub] as $key => $value) {
179 if (isset($legacyMap[$sub .'.'. $key])) {
180 $configKey = $legacyMap[$sub .'.'. $key];
181 } else {
182 $configKey = $sub .'.'. $key;
183 }
184 $this->conf->set($configKey, $value);
185 }
186 }
187
188 try{
189 $this->conf->write($this->isLoggedIn);
190 return true;
191 } catch (IOException $e) {
192 error_log($e->getMessage());
193 return false;
194 }
195 }
196
197 /**
198 * Escape settings which have been manually escaped in every request in previous versions:
199 * - general.title
200 * - general.header_link
201 * - extras.redirector
202 *
203 * @return bool true if the update is successful, false otherwise.
204 */
205 public function escapeUnescapedConfig()
206 {
207 try {
208 $this->conf->set('general.title', escape($this->conf->get('general.title')));
209 $this->conf->set('general.header_link', escape($this->conf->get('general.header_link')));
210 $this->conf->set('redirector.url', escape($this->conf->get('redirector.url')));
211 $this->conf->write($this->isLoggedIn);
212 } catch (Exception $e) {
213 error_log($e->getMessage());
214 return false;
215 }
147 return true; 216 return true;
148 } 217 }
149} 218}
@@ -203,7 +272,6 @@ class UpdaterException extends Exception
203 } 272 }
204} 273}
205 274
206
207/** 275/**
208 * Read the updates file, and return already done updates. 276 * Read the updates file, and return already done updates.
209 * 277 *
diff --git a/application/Utils.php b/application/Utils.php
index da521cce..9a8ca6d1 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -273,4 +273,4 @@ function autoLocale($headerLocale)
273 } 273 }
274 } 274 }
275 setlocale(LC_ALL, $attempts); 275 setlocale(LC_ALL, $attempts);
276} \ No newline at end of file 276}
diff --git a/application/config/ConfigIO.php b/application/config/ConfigIO.php
new file mode 100644
index 00000000..2b68fe6a
--- /dev/null
+++ b/application/config/ConfigIO.php
@@ -0,0 +1,33 @@
1<?php
2
3/**
4 * Interface ConfigIO
5 *
6 * This describes how Config types should store their configuration.
7 */
8interface ConfigIO
9{
10 /**
11 * Read configuration.
12 *
13 * @param string $filepath Config file absolute path.
14 *
15 * @return array All configuration in an array.
16 */
17 function read($filepath);
18
19 /**
20 * Write configuration.
21 *
22 * @param string $filepath Config file absolute path.
23 * @param array $conf All configuration in an array.
24 */
25 function write($filepath, $conf);
26
27 /**
28 * Get config file extension according to config type.
29 *
30 * @return string Config file extension.
31 */
32 function getExtension();
33}
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php
new file mode 100644
index 00000000..d07fefee
--- /dev/null
+++ b/application/config/ConfigJson.php
@@ -0,0 +1,78 @@
1<?php
2
3/**
4 * Class ConfigJson (ConfigIO implementation)
5 *
6 * Handle Shaarli's JSON configuration file.
7 */
8class ConfigJson implements ConfigIO
9{
10 /**
11 * @inheritdoc
12 */
13 function read($filepath)
14 {
15 if (! is_readable($filepath)) {
16 return array();
17 }
18 $data = file_get_contents($filepath);
19 $data = str_replace(self::getPhpHeaders(), '', $data);
20 $data = str_replace(self::getPhpSuffix(), '', $data);
21 $data = json_decode($data, true);
22 if ($data === null) {
23 $error = json_last_error();
24 throw new Exception('An error occured while parsing JSON file: error code #'. $error);
25 }
26 return $data;
27 }
28
29 /**
30 * @inheritdoc
31 */
32 function write($filepath, $conf)
33 {
34 // JSON_PRETTY_PRINT is available from PHP 5.4.
35 $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
36 $data = self::getPhpHeaders() . json_encode($conf, $print) . self::getPhpSuffix();
37 if (!file_put_contents($filepath, $data)) {
38 throw new IOException(
39 $filepath,
40 'Shaarli could not create the config file.
41 Please make sure Shaarli has the right to write in the folder is it installed in.'
42 );
43 }
44 }
45
46 /**
47 * @inheritdoc
48 */
49 function getExtension()
50 {
51 return '.json.php';
52 }
53
54 /**
55 * The JSON data is wrapped in a PHP file for security purpose.
56 * This way, even if the file is accessible, credentials and configuration won't be exposed.
57 *
58 * Note: this isn't a static field because concatenation isn't supported in field declaration before PHP 5.6.
59 *
60 * @return string PHP start tag and comment tag.
61 */
62 public static function getPhpHeaders()
63 {
64 return '<?php /*'. PHP_EOL;
65 }
66
67 /**
68 * Get PHP comment closing tags.
69 *
70 * Static method for consistency with getPhpHeaders.
71 *
72 * @return string PHP comment closing.
73 */
74 public static function getPhpSuffix()
75 {
76 return PHP_EOL . '*/ ?>';
77 }
78}
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
new file mode 100644
index 00000000..ff41772a
--- /dev/null
+++ b/application/config/ConfigManager.php
@@ -0,0 +1,392 @@
1<?php
2
3// FIXME! Namespaces...
4require_once 'ConfigIO.php';
5require_once 'ConfigJson.php';
6require_once 'ConfigPhp.php';
7
8/**
9 * Class ConfigManager
10 *
11 * Manages all Shaarli's settings.
12 * See the documentation for more information on settings:
13 * - doc/Shaarli-configuration.html
14 * - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration
15 */
16class ConfigManager
17{
18 /**
19 * @var string Flag telling a setting is not found.
20 */
21 protected static $NOT_FOUND = 'NOT_FOUND';
22
23 /**
24 * @var string Config folder.
25 */
26 protected $configFile;
27
28 /**
29 * @var array Loaded config array.
30 */
31 protected $loadedConfig;
32
33 /**
34 * @var ConfigIO implementation instance.
35 */
36 protected $configIO;
37
38 /**
39 * Constructor.
40 */
41 public function __construct($configFile = 'data/config')
42 {
43 $this->configFile = $configFile;
44 $this->initialize();
45 }
46
47 /**
48 * Reset the ConfigManager instance.
49 */
50 public function reset()
51 {
52 $this->initialize();
53 }
54
55 /**
56 * Rebuild the loaded config array from config files.
57 */
58 public function reload()
59 {
60 $this->load();
61 }
62
63 /**
64 * Initialize the ConfigIO and loaded the conf.
65 */
66 protected function initialize()
67 {
68 if (file_exists($this->configFile . '.php')) {
69 $this->configIO = new ConfigPhp();
70 } else {
71 $this->configIO = new ConfigJson();
72 }
73 $this->load();
74 }
75
76 /**
77 * Load configuration in the ConfigurationManager.
78 */
79 protected function load()
80 {
81 $this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
82 $this->setDefaultValues();
83 }
84
85 /**
86 * Get a setting.
87 *
88 * Supports nested settings with dot separated keys.
89 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
90 * or in JSON:
91 * { "config": { "stuff": {"option": "mysetting" } } } }
92 *
93 * @param string $setting Asked setting, keys separated with dots.
94 * @param string $default Default value if not found.
95 *
96 * @return mixed Found setting, or the default value.
97 */
98 public function get($setting, $default = '')
99 {
100 // During the ConfigIO transition, map legacy settings to the new ones.
101 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
102 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
103 }
104
105 $settings = explode('.', $setting);
106 $value = self::getConfig($settings, $this->loadedConfig);
107 if ($value === self::$NOT_FOUND) {
108 return $default;
109 }
110 return $value;
111 }
112
113 /**
114 * Set a setting, and eventually write it.
115 *
116 * Supports nested settings with dot separated keys.
117 *
118 * @param string $setting Asked setting, keys separated with dots.
119 * @param string $value Value to set.
120 * @param bool $write Write the new setting in the config file, default false.
121 * @param bool $isLoggedIn User login state, default false.
122 *
123 * @throws Exception Invalid
124 */
125 public function set($setting, $value, $write = false, $isLoggedIn = false)
126 {
127 if (empty($setting) || ! is_string($setting)) {
128 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
129 }
130
131 // During the ConfigIO transition, map legacy settings to the new ones.
132 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
133 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
134 }
135
136 $settings = explode('.', $setting);
137 self::setConfig($settings, $value, $this->loadedConfig);
138 if ($write) {
139 $this->write($isLoggedIn);
140 }
141 }
142
143 /**
144 * Check if a settings exists.
145 *
146 * Supports nested settings with dot separated keys.
147 *
148 * @param string $setting Asked setting, keys separated with dots.
149 *
150 * @return bool true if the setting exists, false otherwise.
151 */
152 public function exists($setting)
153 {
154 // During the ConfigIO transition, map legacy settings to the new ones.
155 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
156 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
157 }
158
159 $settings = explode('.', $setting);
160 $value = self::getConfig($settings, $this->loadedConfig);
161 if ($value === self::$NOT_FOUND) {
162 return false;
163 }
164 return true;
165 }
166
167 /**
168 * Call the config writer.
169 *
170 * @param bool $isLoggedIn User login state.
171 *
172 * @return bool True if the configuration has been successfully written, false otherwise.
173 *
174 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
175 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
176 * @throws IOException: an error occurred while writing the new config file.
177 */
178 public function write($isLoggedIn)
179 {
180 // These fields are required in configuration.
181 $mandatoryFields = array(
182 'credentials.login',
183 'credentials.hash',
184 'credentials.salt',
185 'security.session_protection_disabled',
186 'general.timezone',
187 'general.title',
188 'general.header_link',
189 'privacy.default_private_links',
190 'redirector.url',
191 );
192
193 // Only logged in user can alter config.
194 if (is_file($this->getConfigFileExt()) && !$isLoggedIn) {
195 throw new UnauthorizedConfigException();
196 }
197
198 // Check that all mandatory fields are provided in $conf.
199 foreach ($mandatoryFields as $field) {
200 if (! $this->exists($field)) {
201 throw new MissingFieldConfigException($field);
202 }
203 }
204
205 return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig);
206 }
207
208 /**
209 * Set the config file path (without extension).
210 *
211 * @param string $configFile File path.
212 */
213 public function setConfigFile($configFile)
214 {
215 $this->configFile = $configFile;
216 }
217
218 /**
219 * Return the configuration file path (without extension).
220 *
221 * @return string Config path.
222 */
223 public function getConfigFile()
224 {
225 return $this->configFile;
226 }
227
228 /**
229 * Get the configuration file path with its extension.
230 *
231 * @return string Config file path.
232 */
233 public function getConfigFileExt()
234 {
235 return $this->configFile . $this->configIO->getExtension();
236 }
237
238 /**
239 * Recursive function which find asked setting in the loaded config.
240 *
241 * @param array $settings Ordered array which contains keys to find.
242 * @param array $conf Loaded settings, then sub-array.
243 *
244 * @return mixed Found setting or NOT_FOUND flag.
245 */
246 protected static function getConfig($settings, $conf)
247 {
248 if (!is_array($settings) || count($settings) == 0) {
249 return self::$NOT_FOUND;
250 }
251
252 $setting = array_shift($settings);
253 if (!isset($conf[$setting])) {
254 return self::$NOT_FOUND;
255 }
256
257 if (count($settings) > 0) {
258 return self::getConfig($settings, $conf[$setting]);
259 }
260 return $conf[$setting];
261 }
262
263 /**
264 * Recursive function which find asked setting in the loaded config.
265 *
266 * @param array $settings Ordered array which contains keys to find.
267 * @param mixed $value
268 * @param array $conf Loaded settings, then sub-array.
269 *
270 * @return mixed Found setting or NOT_FOUND flag.
271 */
272 protected static function setConfig($settings, $value, &$conf)
273 {
274 if (!is_array($settings) || count($settings) == 0) {
275 return self::$NOT_FOUND;
276 }
277
278 $setting = array_shift($settings);
279 if (count($settings) > 0) {
280 return self::setConfig($settings, $value, $conf[$setting]);
281 }
282 $conf[$setting] = $value;
283 }
284
285 /**
286 * Set a bunch of default values allowing Shaarli to start without a config file.
287 */
288 protected function setDefaultValues()
289 {
290 $this->setEmpty('resource.data_dir', 'data');
291 $this->setEmpty('resource.config', 'data/config.php');
292 $this->setEmpty('resource.datastore', 'data/datastore.php');
293 $this->setEmpty('resource.ban_file', 'data/ipbans.php');
294 $this->setEmpty('resource.updates', 'data/updates.txt');
295 $this->setEmpty('resource.log', 'data/log.txt');
296 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
297 $this->setEmpty('resource.raintpl_tpl', 'tpl/');
298 $this->setEmpty('resource.raintpl_tmp', 'tmp/');
299 $this->setEmpty('resource.thumbnails_cache', 'cache');
300 $this->setEmpty('resource.page_cache', 'pagecache');
301
302 $this->setEmpty('security.ban_after', 4);
303 $this->setEmpty('security.ban_duration', 1800);
304 $this->setEmpty('security.session_protection_disabled', false);
305 $this->setEmpty('security.open_shaarli', false);
306
307 $this->setEmpty('general.header_link', '?');
308 $this->setEmpty('general.links_per_page', 20);
309 $this->setEmpty('general.enabled_plugins', array('qrcode'));
310
311 $this->setEmpty('updates.check_updates', false);
312 $this->setEmpty('updates.check_updates_branch', 'stable');
313 $this->setEmpty('updates.check_updates_interval', 86400);
314
315 $this->setEmpty('feed.rss_permalinks', true);
316 $this->setEmpty('feed.show_atom', false);
317
318 $this->setEmpty('privacy.default_private_links', false);
319 $this->setEmpty('privacy.hide_public_links', false);
320 $this->setEmpty('privacy.hide_timestamps', false);
321
322 $this->setEmpty('thumbnail.enable_thumbnails', true);
323 $this->setEmpty('thumbnail.enable_localcache', true);
324
325 $this->setEmpty('redirector.url', '');
326 $this->setEmpty('redirector.encode_url', true);
327
328 $this->setEmpty('plugins', array());
329 }
330
331 /**
332 * Set only if the setting does not exists.
333 *
334 * @param string $key Setting key.
335 * @param mixed $value Setting value.
336 */
337 public function setEmpty($key, $value)
338 {
339 if (! $this->exists($key)) {
340 $this->set($key, $value);
341 }
342 }
343
344 /**
345 * @return ConfigIO
346 */
347 public function getConfigIO()
348 {
349 return $this->configIO;
350 }
351
352 /**
353 * @param ConfigIO $configIO
354 */
355 public function setConfigIO($configIO)
356 {
357 $this->configIO = $configIO;
358 }
359}
360
361/**
362 * Exception used if a mandatory field is missing in given configuration.
363 */
364class MissingFieldConfigException extends Exception
365{
366 public $field;
367
368 /**
369 * Construct exception.
370 *
371 * @param string $field field name missing.
372 */
373 public function __construct($field)
374 {
375 $this->field = $field;
376 $this->message = 'Configuration value is required for '. $this->field;
377 }
378}
379
380/**
381 * Exception used if an unauthorized attempt to edit configuration has been made.
382 */
383class UnauthorizedConfigException extends Exception
384{
385 /**
386 * Construct exception.
387 */
388 public function __construct()
389 {
390 $this->message = 'You are not authorized to alter config.';
391 }
392}
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
new file mode 100644
index 00000000..27187b66
--- /dev/null
+++ b/application/config/ConfigPhp.php
@@ -0,0 +1,132 @@
1<?php
2
3/**
4 * Class ConfigPhp (ConfigIO implementation)
5 *
6 * Handle Shaarli's legacy PHP configuration file.
7 * Note: this is only designed to support the transition to JSON configuration.
8 */
9class ConfigPhp implements ConfigIO
10{
11 /**
12 * @var array List of config key without group.
13 */
14 public static $ROOT_KEYS = array(
15 'login',
16 'hash',
17 'salt',
18 'timezone',
19 'title',
20 'titleLink',
21 'redirector',
22 'disablesessionprotection',
23 'privateLinkByDefault',
24 );
25
26 /**
27 * Map legacy config keys with the new ones.
28 * If ConfigPhp is used, getting <newkey> will actually look for <legacykey>.
29 * The Updater will use this array to transform keys when switching to JSON.
30 *
31 * @var array current key => legacy key.
32 */
33 public static $LEGACY_KEYS_MAPPING = array(
34 'credentials.login' => 'login',
35 'credentials.hash' => 'hash',
36 'credentials.salt' => 'salt',
37 'resource.data_dir' => 'config.DATADIR',
38 'resource.config' => 'config.CONFIG_FILE',
39 'resource.datastore' => 'config.DATASTORE',
40 'resource.updates' => 'config.UPDATES_FILE',
41 'resource.log' => 'config.LOG_FILE',
42 'resource.update_check' => 'config.UPDATECHECK_FILENAME',
43 'resource.raintpl_tpl' => 'config.RAINTPL_TPL',
44 'resource.raintpl_tmp' => 'config.RAINTPL_TMP',
45 'resource.thumbnails_cache' => 'config.CACHEDIR',
46 'resource.page_cache' => 'config.PAGECACHE',
47 'resource.ban_file' => 'config.IPBANS_FILENAME',
48 'security.session_protection_disabled' => 'disablesessionprotection',
49 'security.ban_after' => 'config.BAN_AFTER',
50 'security.ban_duration' => 'config.BAN_DURATION',
51 'general.title' => 'title',
52 'general.timezone' => 'timezone',
53 'general.header_link' => 'titleLink',
54 'updates.check_updates' => 'config.ENABLE_UPDATECHECK',
55 'updates.check_updates_branch' => 'config.UPDATECHECK_BRANCH',
56 'updates.check_updates_interval' => 'config.UPDATECHECK_INTERVAL',
57 'privacy.default_private_links' => 'privateLinkByDefault',
58 'feed.rss_permalinks' => 'config.ENABLE_RSS_PERMALINKS',
59 'general.links_per_page' => 'config.LINKS_PER_PAGE',
60 'thumbnail.enable_thumbnails' => 'config.ENABLE_THUMBNAILS',
61 'thumbnail.enable_localcache' => 'config.ENABLE_LOCALCACHE',
62 'general.enabled_plugins' => 'config.ENABLED_PLUGINS',
63 'redirector.url' => 'redirector',
64 'redirector.encode_url' => 'config.REDIRECTOR_URLENCODE',
65 'feed.show_atom' => 'config.SHOW_ATOM',
66 'privacy.hide_public_links' => 'config.HIDE_PUBLIC_LINKS',
67 'privacy.hide_timestamps' => 'config.HIDE_TIMESTAMPS',
68 'security.open_shaarli' => 'config.OPEN_SHAARLI',
69 );
70
71 /**
72 * @inheritdoc
73 */
74 function read($filepath)
75 {
76 if (! file_exists($filepath) || ! is_readable($filepath)) {
77 return array();
78 }
79
80 include $filepath;
81
82 $out = array();
83 foreach (self::$ROOT_KEYS as $key) {
84 $out[$key] = $GLOBALS[$key];
85 }
86 $out['config'] = $GLOBALS['config'];
87 $out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array();
88 return $out;
89 }
90
91 /**
92 * @inheritdoc
93 */
94 function write($filepath, $conf)
95 {
96 $configStr = '<?php '. PHP_EOL;
97 foreach (self::$ROOT_KEYS as $key) {
98 if (isset($conf[$key])) {
99 $configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL;
100 }
101 }
102
103 // Store all $conf['config']
104 foreach ($conf['config'] as $key => $value) {
105 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL;
106 }
107
108 if (isset($conf['plugins'])) {
109 foreach ($conf['plugins'] as $key => $value) {
110 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL;
111 }
112 }
113
114 if (!file_put_contents($filepath, $configStr)
115 || strcmp(file_get_contents($filepath), $configStr) != 0
116 ) {
117 throw new IOException(
118 $filepath,
119 'Shaarli could not create the config file.
120 Please make sure Shaarli has the right to write in the folder is it installed in.'
121 );
122 }
123 }
124
125 /**
126 * @inheritdoc
127 */
128 function getExtension()
129 {
130 return '.php';
131 }
132}
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php
new file mode 100644
index 00000000..047d2b03
--- /dev/null
+++ b/application/config/ConfigPlugin.php
@@ -0,0 +1,120 @@
1<?php
2/**
3 * Plugin configuration helper functions.
4 *
5 * Note: no access to configuration files here.
6 */
7
8/**
9 * Process plugin administration form data and save it in an array.
10 *
11 * @param array $formData Data sent by the plugin admin form.
12 *
13 * @return array New list of enabled plugin, ordered.
14 *
15 * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
16 */
17function save_plugin_config($formData)
18{
19 // Make sure there are no duplicates in orders.
20 if (!validate_plugin_order($formData)) {
21 throw new PluginConfigOrderException();
22 }
23
24 $plugins = array();
25 $newEnabledPlugins = array();
26 foreach ($formData as $key => $data) {
27 if (startsWith($key, 'order')) {
28 continue;
29 }
30
31 // If there is no order, it means a disabled plugin has been enabled.
32 if (isset($formData['order_' . $key])) {
33 $plugins[(int) $formData['order_' . $key]] = $key;
34 }
35 else {
36 $newEnabledPlugins[] = $key;
37 }
38 }
39
40 // New enabled plugins will be added at the end of order.
41 $plugins = array_merge($plugins, $newEnabledPlugins);
42
43 // Sort plugins by order.
44 if (!ksort($plugins)) {
45 throw new PluginConfigOrderException();
46 }
47
48 $finalPlugins = array();
49 // Make plugins order continuous.
50 foreach ($plugins as $plugin) {
51 $finalPlugins[] = $plugin;
52 }
53
54 return $finalPlugins;
55}
56
57/**
58 * Validate plugin array submitted.
59 * Will fail if there is duplicate orders value.
60 *
61 * @param array $formData Data from submitted form.
62 *
63 * @return bool true if ok, false otherwise.
64 */
65function validate_plugin_order($formData)
66{
67 $orders = array();
68 foreach ($formData as $key => $value) {
69 // No duplicate order allowed.
70 if (in_array($value, $orders)) {
71 return false;
72 }
73
74 if (startsWith($key, 'order')) {
75 $orders[] = $value;
76 }
77 }
78
79 return true;
80}
81
82/**
83 * Affect plugin parameters values into plugins array.
84 *
85 * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
86 * @param mixed $conf Plugins configuration.
87 *
88 * @return mixed Updated $plugins array.
89 */
90function load_plugin_parameter_values($plugins, $conf)
91{
92 $out = $plugins;
93 foreach ($plugins as $name => $plugin) {
94 if (empty($plugin['parameters'])) {
95 continue;
96 }
97
98 foreach ($plugin['parameters'] as $key => $param) {
99 if (!empty($conf[$key])) {
100 $out[$name]['parameters'][$key] = $conf[$key];
101 }
102 }
103 }
104
105 return $out;
106}
107
108/**
109 * Exception used if an error occur while saving plugin configuration.
110 */
111class PluginConfigOrderException extends Exception
112{
113 /**
114 * Construct exception.
115 */
116 public function __construct()
117 {
118 $this->message = 'An error occurred while trying to save plugins loading order.';
119 }
120}
diff --git a/index.php b/index.php
index 7465c41f..b9576de8 100644
--- a/index.php
+++ b/index.php
@@ -22,114 +22,13 @@ if (date_default_timezone_get() == '') {
22 date_default_timezone_set('UTC'); 22 date_default_timezone_set('UTC');
23} 23}
24 24
25/* -----------------------------------------------------------------------------
26 * Hardcoded parameters
27 * You should not touch any code below (or at your own risks!)
28 * (These parameters can be overwritten by editing the file /data/config.php)
29 * -----------------------------------------------------------------------------
30 */
31
32/*
33 * Shaarli directories & configuration files
34 */
35// Data subdirectory
36$GLOBALS['config']['DATADIR'] = 'data';
37
38// Main configuration file
39$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php';
40
41// Link datastore
42$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php';
43
44// Banned IPs
45$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php';
46
47// Processed updates file.
48$GLOBALS['config']['UPDATES_FILE'] = $GLOBALS['config']['DATADIR'].'/updates.txt';
49
50// Access log
51$GLOBALS['config']['LOG_FILE'] = $GLOBALS['config']['DATADIR'].'/log.txt';
52
53// For updates check of Shaarli
54$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
55
56// Set ENABLE_UPDATECHECK to disabled by default.
57$GLOBALS['config']['ENABLE_UPDATECHECK'] = false;
58
59// RainTPL cache directory (keep the trailing slash!)
60$GLOBALS['config']['RAINTPL_TMP'] = 'tmp/';
61// Raintpl template directory (keep the trailing slash!)
62$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
63
64// Thumbnail cache directory
65$GLOBALS['config']['CACHEDIR'] = 'cache';
66
67// Atom & RSS feed cache directory
68$GLOBALS['config']['PAGECACHE'] = 'pagecache';
69
70/*
71 * Global configuration
72 */
73// Ban IP after this many failures
74$GLOBALS['config']['BAN_AFTER'] = 4;
75// Ban duration for IP address after login failures (in seconds)
76$GLOBALS['config']['BAN_DURATION'] = 1800;
77
78// Feed options
79// Enable RSS permalinks by default.
80// This corresponds to the default behavior of shaarli before this was added as an option.
81$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true;
82// If true, an extra "ATOM feed" button will be displayed in the toolbar
83$GLOBALS['config']['SHOW_ATOM'] = false;
84
85// Link display options
86$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
87$GLOBALS['config']['HIDE_TIMESTAMPS'] = false;
88$GLOBALS['config']['LINKS_PER_PAGE'] = 20;
89
90// Open Shaarli (true): anyone can add/edit/delete links without having to login
91$GLOBALS['config']['OPEN_SHAARLI'] = false;
92
93// Thumbnails
94// Display thumbnails in links
95$GLOBALS['config']['ENABLE_THUMBNAILS'] = true;
96// Store thumbnails in a local cache
97$GLOBALS['config']['ENABLE_LOCALCACHE'] = true;
98
99// Update check frequency for Shaarli. 86400 seconds=24 hours
100$GLOBALS['config']['UPDATECHECK_BRANCH'] = 'stable';
101$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400;
102
103$GLOBALS['config']['REDIRECTOR_URLENCODE'] = true;
104
105/*
106 * Plugin configuration
107 *
108 * Warning: order matters!
109 *
110 * These settings may be be overriden in:
111 * - data/config.php
112 * - each plugin's configuration file
113 */
114//$GLOBALS['config']['ENABLED_PLUGINS'] = array(
115// 'qrcode', 'archiveorg', 'readityourself', 'demo_plugin', 'playvideos',
116// 'wallabag', 'markdown', 'addlink_toolbar',
117//);
118$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode');
119
120// Initialize plugin parameters array.
121$GLOBALS['plugins'] = array();
122
123// PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable.
124$GLOBALS['config']['PUBSUBHUB_URL'] = '';
125
126/* 25/*
127 * PHP configuration 26 * PHP configuration
128 */ 27 */
129define('shaarli_version', '0.7.0'); 28define('shaarli_version', '0.7.0');
130 29
131// http://server.com/x/shaarli --> /shaarli/ 30// http://server.com/x/shaarli --> /shaarli/
132define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0))); 31define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
133 32
134// High execution time in case of problematic imports/exports. 33// High execution time in case of problematic imports/exports.
135ini_set('max_input_time','60'); 34ini_set('max_input_time','60');
@@ -144,17 +43,13 @@ error_reporting(E_ALL^E_WARNING);
144// See all errors (for debugging only) 43// See all errors (for debugging only)
145//error_reporting(-1); 44//error_reporting(-1);
146 45
147/*
148 * User configuration
149 */
150if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
151 require_once $GLOBALS['config']['CONFIG_FILE'];
152}
153 46
154// Shaarli library 47// Shaarli library
155require_once 'application/ApplicationUtils.php'; 48require_once 'application/ApplicationUtils.php';
156require_once 'application/Cache.php'; 49require_once 'application/Cache.php';
157require_once 'application/CachedPage.php'; 50require_once 'application/CachedPage.php';
51require_once 'application/config/ConfigManager.php';
52require_once 'application/config/ConfigPlugin.php';
158require_once 'application/FeedBuilder.php'; 53require_once 'application/FeedBuilder.php';
159require_once 'application/FileUtils.php'; 54require_once 'application/FileUtils.php';
160require_once 'application/HttpUtils.php'; 55require_once 'application/HttpUtils.php';
@@ -166,10 +61,10 @@ require_once 'application/PageBuilder.php';
166require_once 'application/TimeZone.php'; 61require_once 'application/TimeZone.php';
167require_once 'application/Url.php'; 62require_once 'application/Url.php';
168require_once 'application/Utils.php'; 63require_once 'application/Utils.php';
169require_once 'application/Config.php';
170require_once 'application/PluginManager.php'; 64require_once 'application/PluginManager.php';
171require_once 'application/Router.php'; 65require_once 'application/Router.php';
172require_once 'application/Updater.php'; 66require_once 'application/Updater.php';
67require_once 'inc/rain.tpl.class.php';
173 68
174// Ensure the PHP version is supported 69// Ensure the PHP version is supported
175try { 70try {
@@ -210,15 +105,18 @@ if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
210 $_COOKIE['shaarli'] = session_id(); 105 $_COOKIE['shaarli'] = session_id();
211} 106}
212 107
213include "inc/rain.tpl.class.php"; //include Rain TPL 108$conf = new ConfigManager();
214raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory 109$conf->setEmpty('general.timezone', date_default_timezone_get());
215raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory 110$conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER)));
111RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl'); // template directory
112RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory
216 113
217$pluginManager = PluginManager::getInstance(); 114$pluginManager = new PluginManager($conf);
218$pluginManager->load($GLOBALS['config']['ENABLED_PLUGINS']); 115$pluginManager->load($conf->get('general.enabled_plugins'));
219 116
220ob_start(); // Output buffering for the page cache. 117date_default_timezone_set($conf->get('general.timezone', 'UTC'));
221 118
119ob_start(); // Output buffering for the page cache.
222 120
223// In case stupid admin has left magic_quotes enabled in php.ini: 121// In case stupid admin has left magic_quotes enabled in php.ini:
224if (get_magic_quotes_gpc()) 122if (get_magic_quotes_gpc())
@@ -235,18 +133,9 @@ header("Cache-Control: no-store, no-cache, must-revalidate");
235header("Cache-Control: post-check=0, pre-check=0", false); 133header("Cache-Control: post-check=0, pre-check=0", false);
236header("Pragma: no-cache"); 134header("Pragma: no-cache");
237 135
238// Handling of old config file which do not have the new parameters. 136if (! is_file($conf->getConfigFileExt())) {
239if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
240if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
241if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
242if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
243if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
244if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
245// I really need to rewrite Shaarli with a proper configuation manager.
246
247if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
248 // Ensure Shaarli has proper access to its resources 137 // Ensure Shaarli has proper access to its resources
249 $errors = ApplicationUtils::checkResourcePermissions($GLOBALS['config']); 138 $errors = ApplicationUtils::checkResourcePermissions($conf);
250 139
251 if ($errors != array()) { 140 if ($errors != array()) {
252 $message = '<p>Insufficient permissions:</p><ul>'; 141 $message = '<p>Insufficient permissions:</p><ul>';
@@ -262,15 +151,11 @@ if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
262 } 151 }
263 152
264 // Display the installation form if no existing config is found 153 // Display the installation form if no existing config is found
265 install(); 154 install($conf);
266} 155}
267 156
268$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : '';
269$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : '';
270$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : '';
271
272// a token depending of deployment salt, user password, and the current ip 157// a token depending of deployment salt, user password, and the current ip
273define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GLOBALS['salt'])); 158define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
274 159
275// Sniff browser language and set date format accordingly. 160// Sniff browser language and set date format accordingly.
276if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 161if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
@@ -278,17 +163,21 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
278} 163}
279header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling. 164header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling.
280 165
281//================================================================================================== 166/**
282// Checking session state (i.e. is the user still logged in) 167 * Checking session state (i.e. is the user still logged in)
283//================================================================================================== 168 *
284 169 * @param ConfigManager $conf The configuration manager.
285function setup_login_state() { 170 *
286 if ($GLOBALS['config']['OPEN_SHAARLI']) { 171 * @return bool: true if the user is logged in, false otherwise.
172 */
173function setup_login_state($conf)
174{
175 if ($conf->get('security.open_shaarli')) {
287 return true; 176 return true;
288 } 177 }
289 $userIsLoggedIn = false; // By default, we do not consider the user as logged in; 178 $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
290 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met. 179 $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
291 if (!isset($GLOBALS['login'])) { 180 if (! $conf->exists('credentials.login')) {
292 $userIsLoggedIn = false; // Shaarli is not configured yet. 181 $userIsLoggedIn = false; // Shaarli is not configured yet.
293 $loginFailure = true; 182 $loginFailure = true;
294 } 183 }
@@ -296,13 +185,13 @@ function setup_login_state() {
296 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN && 185 $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
297 !$loginFailure) 186 !$loginFailure)
298 { 187 {
299 fillSessionInfo(); 188 fillSessionInfo($conf);
300 $userIsLoggedIn = true; 189 $userIsLoggedIn = true;
301 } 190 }
302 // If session does not exist on server side, or IP address has changed, or session has expired, logout. 191 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
303 if (empty($_SESSION['uid']) || 192 if (empty($_SESSION['uid'])
304 ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || 193 || ($conf->get('security.session_protection_disabled') == false && $_SESSION['ip'] != allIPs())
305 time() >= $_SESSION['expires_on']) 194 || time() >= $_SESSION['expires_on'])
306 { 195 {
307 logout(); 196 logout();
308 $userIsLoggedIn = false; 197 $userIsLoggedIn = false;
@@ -320,22 +209,26 @@ function setup_login_state() {
320 209
321 return $userIsLoggedIn; 210 return $userIsLoggedIn;
322} 211}
323$userIsLoggedIn = setup_login_state(); 212$userIsLoggedIn = setup_login_state($conf);
324 213
325// ------------------------------------------------------------------------------------------ 214/**
326// PubSubHubbub protocol support (if enabled) [UNTESTED] 215 * PubSubHubbub protocol support (if enabled) [UNTESTED]
327// (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ ) 216 * (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ )
328if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) include './publisher.php'; 217 *
329function pubsubhub() 218 * @param ConfigManager $conf Configuration Manager instance.
219 */
220function pubsubhub($conf)
330{ 221{
331 if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) 222 $pshUrl = $conf->get('config.PUBSUBHUB_URL');
223 if (!empty($pshUrl))
332 { 224 {
333 $p = new Publisher($GLOBALS['config']['PUBSUBHUB_URL']); 225 include_once './publisher.php';
334 $topic_url = array ( 226 $p = new Publisher($pshUrl);
335 index_url($_SERVER).'?do=atom', 227 $topic_url = array (
336 index_url($_SERVER).'?do=rss' 228 index_url($_SERVER).'?do=atom',
337 ); 229 index_url($_SERVER).'?do=rss'
338 $p->publish_update($topic_url); 230 );
231 $p->publish_update($topic_url);
339 } 232 }
340} 233}
341 234
@@ -345,32 +238,46 @@ function pubsubhub()
345// Returns the IP address of the client (Used to prevent session cookie hijacking.) 238// Returns the IP address of the client (Used to prevent session cookie hijacking.)
346function allIPs() 239function allIPs()
347{ 240{
348 $ip = $_SERVER["REMOTE_ADDR"]; 241 $ip = $_SERVER['REMOTE_ADDR'];
349 // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy. 242 // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
350 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; } 243 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
351 if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; } 244 if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
352 return $ip; 245 return $ip;
353} 246}
354 247
355function fillSessionInfo() { 248/**
249 * Load user session.
250 *
251 * @param ConfigManager $conf Configuration Manager instance.
252 */
253function fillSessionInfo($conf)
254{
356 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid) 255 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
357 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked. 256 $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
358 $_SESSION['username']=$GLOBALS['login']; 257 $_SESSION['username']= $conf->get('credentials.login');
359 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration. 258 $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
360} 259}
361 260
362// Check that user/password is correct. 261/**
363function check_auth($login,$password) 262 * Check that user/password is correct.
263 *
264 * @param string $login Username
265 * @param string $password User password
266 * @param ConfigManager $conf Configuration Manager instance.
267 *
268 * @return bool: authentication successful or not.
269 */
270function check_auth($login, $password, $conf)
364{ 271{
365 $hash = sha1($password.$login.$GLOBALS['salt']); 272 $hash = sha1($password . $login . $conf->get('credentials.salt'));
366 if ($login==$GLOBALS['login'] && $hash==$GLOBALS['hash']) 273 if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
367 { // Login/password is correct. 274 { // Login/password is correct.
368 fillSessionInfo(); 275 fillSessionInfo($conf);
369 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Login successful'); 276 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
370 return True; 277 return true;
371 } 278 }
372 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login); 279 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login);
373 return False; 280 return false;
374} 281}
375 282
376// Returns true if the user is logged in. 283// Returns true if the user is logged in.
@@ -395,34 +302,62 @@ function logout() {
395// ------------------------------------------------------------------------------------------ 302// ------------------------------------------------------------------------------------------
396// Brute force protection system 303// Brute force protection system
397// Several consecutive failed logins will ban the IP address for 30 minutes. 304// Several consecutive failed logins will ban the IP address for 30 minutes.
398if (!is_file($GLOBALS['config']['IPBANS_FILENAME'])) file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"); 305if (!is_file($conf->get('resource.ban_file', 'data/ipbans.php'))) {
399include $GLOBALS['config']['IPBANS_FILENAME']; 306 // FIXME! globals
400// Signal a failed login. Will ban the IP if too many failures: 307 file_put_contents(
401function ban_loginFailed() 308 $conf->get('resource.ban_file', 'data/ipbans.php'),
309 "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"
310 );
311}
312include $conf->get('resource.ban_file', 'data/ipbans.php');
313/**
314 * Signal a failed login. Will ban the IP if too many failures:
315 *
316 * @param ConfigManager $conf Configuration Manager instance.
317 */
318function ban_loginFailed($conf)
402{ 319{
403 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 320 $ip = $_SERVER['REMOTE_ADDR'];
321 $gb = $GLOBALS['IPBANS'];
404 if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0; 322 if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0;
405 $gb['FAILURES'][$ip]++; 323 $gb['FAILURES'][$ip]++;
406 if ($gb['FAILURES'][$ip]>($GLOBALS['config']['BAN_AFTER']-1)) 324 if ($gb['FAILURES'][$ip] > ($conf->get('security.ban_after') - 1))
407 { 325 {
408 $gb['BANS'][$ip]=time()+$GLOBALS['config']['BAN_DURATION']; 326 $gb['BANS'][$ip] = time() + $conf->get('security.ban_after', 1800);
409 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'IP address banned from login'); 327 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'IP address banned from login');
410 } 328 }
411 $GLOBALS['IPBANS'] = $gb; 329 $GLOBALS['IPBANS'] = $gb;
412 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 330 file_put_contents(
331 $conf->get('resource.ban_file', 'data/ipbans.php'),
332 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
333 );
413} 334}
414 335
415// Signals a successful login. Resets failed login counter. 336/**
416function ban_loginOk() 337 * Signals a successful login. Resets failed login counter.
338 *
339 * @param ConfigManager $conf Configuration Manager instance.
340 */
341function ban_loginOk($conf)
417{ 342{
418 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 343 $ip = $_SERVER['REMOTE_ADDR'];
344 $gb = $GLOBALS['IPBANS'];
419 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 345 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
420 $GLOBALS['IPBANS'] = $gb; 346 $GLOBALS['IPBANS'] = $gb;
421 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 347 file_put_contents(
348 $conf->get('resource.ban_file', 'data/ipbans.php'),
349 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
350 );
422} 351}
423 352
424// Checks if the user CAN login. If 'true', the user can try to login. 353/**
425function ban_canLogin() 354 * Checks if the user CAN login. If 'true', the user can try to login.
355 *
356 * @param ConfigManager $conf Configuration Manager instance.
357 *
358 * @return bool: true if the user is allowed to login.
359 */
360function ban_canLogin($conf)
426{ 361{
427 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 362 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
428 if (isset($gb['BANS'][$ip])) 363 if (isset($gb['BANS'][$ip]))
@@ -430,9 +365,12 @@ function ban_canLogin()
430 // User is banned. Check if the ban has expired: 365 // User is banned. Check if the ban has expired:
431 if ($gb['BANS'][$ip]<=time()) 366 if ($gb['BANS'][$ip]<=time())
432 { // Ban expired, user can try to login again. 367 { // Ban expired, user can try to login again.
433 logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], 'Ban lifted.'); 368 logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Ban lifted.');
434 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 369 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
435 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 370 file_put_contents(
371 $conf->get('resource.ban_file', 'data/ipbans.php'),
372 "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"
373 );
436 return true; // Ban has expired, user can login. 374 return true; // Ban has expired, user can login.
437 } 375 }
438 return false; // User is banned. 376 return false; // User is banned.
@@ -444,10 +382,12 @@ function ban_canLogin()
444// Process login form: Check if login/password is correct. 382// Process login form: Check if login/password is correct.
445if (isset($_POST['login'])) 383if (isset($_POST['login']))
446{ 384{
447 if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.'); 385 if (!ban_canLogin($conf)) die('I said: NO. You are banned for the moment. Go away.');
448 if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password']))) 386 if (isset($_POST['password'])
449 { // Login/password is OK. 387 && tokenOk($_POST['token'])
450 ban_loginOk(); 388 && (check_auth($_POST['login'], $_POST['password'], $conf))
389 ) { // Login/password is OK.
390 ban_loginOk($conf);
451 // If user wants to keep the session cookie even after the browser closes: 391 // If user wants to keep the session cookie even after the browser closes:
452 if (!empty($_POST['longlastingsession'])) 392 if (!empty($_POST['longlastingsession']))
453 { 393 {
@@ -495,7 +435,7 @@ if (isset($_POST['login']))
495 } 435 }
496 else 436 else
497 { 437 {
498 ban_loginFailed(); 438 ban_loginFailed($conf);
499 $redir = '&username='. $_POST['login']; 439 $redir = '&username='. $_POST['login'];
500 if (isset($_GET['post'])) { 440 if (isset($_GET['post'])) {
501 $redir .= '&post=' . urlencode($_GET['post']); 441 $redir .= '&post=' . urlencode($_GET['post']);
@@ -543,10 +483,16 @@ function getMaxFileSize()
543// Token should be used in any form which acts on data (create,update,delete,import...). 483// Token should be used in any form which acts on data (create,update,delete,import...).
544if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. 484if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
545 485
546// Returns a token. 486/**
547function getToken() 487 * Returns a token.
488 *
489 * @param ConfigManager $conf Configuration Manager instance.
490 *
491 * @return string token.
492 */
493function getToken($conf)
548{ 494{
549 $rnd = sha1(uniqid('',true).'_'.mt_rand().$GLOBALS['salt']); // We generate a random string. 495 $rnd = sha1(uniqid('', true) .'_'. mt_rand() . $conf->get('credentials.salt')); // We generate a random string.
550 $_SESSION['tokens'][$rnd]=1; // Store it on the server side. 496 $_SESSION['tokens'][$rnd]=1; // Store it on the server side.
551 return $rnd; 497 return $rnd;
552} 498}
@@ -563,15 +509,18 @@ function tokenOk($token)
563 return false; // Wrong token, or already used. 509 return false; // Wrong token, or already used.
564} 510}
565 511
566// ------------------------------------------------------------------------------------------ 512/**
567// Daily RSS feed: 1 RSS entry per day giving all the links on that day. 513 * Daily RSS feed: 1 RSS entry per day giving all the links on that day.
568// Gives the last 7 days (which have links). 514 * Gives the last 7 days (which have links).
569// This RSS feed cannot be filtered. 515 * This RSS feed cannot be filtered.
570function showDailyRSS() { 516 *
517 * @param ConfigManager $conf Configuration Manager instance.
518 */
519function showDailyRSS($conf) {
571 // Cache system 520 // Cache system
572 $query = $_SERVER['QUERY_STRING']; 521 $query = $_SERVER['QUERY_STRING'];
573 $cache = new CachedPage( 522 $cache = new CachedPage(
574 $GLOBALS['config']['PAGECACHE'], 523 $conf->get('config.PAGE_CACHE'),
575 page_url($_SERVER), 524 page_url($_SERVER),
576 startsWith($query,'do=dailyrss') && !isLoggedIn() 525 startsWith($query,'do=dailyrss') && !isLoggedIn()
577 ); 526 );
@@ -584,11 +533,11 @@ function showDailyRSS() {
584 // If cached was not found (or not usable), then read the database and build the response: 533 // If cached was not found (or not usable), then read the database and build the response:
585 // Read links from database (and filter private links if used it not logged in). 534 // Read links from database (and filter private links if used it not logged in).
586 $LINKSDB = new LinkDB( 535 $LINKSDB = new LinkDB(
587 $GLOBALS['config']['DATASTORE'], 536 $conf->get('resource.datastore'),
588 isLoggedIn(), 537 isLoggedIn(),
589 $GLOBALS['config']['HIDE_PUBLIC_LINKS'], 538 $conf->get('privacy.hide_public_links'),
590 $GLOBALS['redirector'], 539 $conf->get('redirector.url'),
591 $GLOBALS['config']['REDIRECTOR_URLENCODE'] 540 $conf->get('redirector.encode_url')
592 ); 541 );
593 542
594 /* Some Shaarlies may have very few links, so we need to look 543 /* Some Shaarlies may have very few links, so we need to look
@@ -600,7 +549,7 @@ function showDailyRSS() {
600 } 549 }
601 rsort($linkdates); 550 rsort($linkdates);
602 $nb_of_days = 7; // We take 7 days. 551 $nb_of_days = 7; // We take 7 days.
603 $today = Date('Ymd'); 552 $today = date('Ymd');
604 $days = array(); 553 $days = array();
605 554
606 foreach ($linkdates as $linkdate) { 555 foreach ($linkdates as $linkdate) {
@@ -622,7 +571,7 @@ function showDailyRSS() {
622 $pageaddr = escape(index_url($_SERVER)); 571 $pageaddr = escape(index_url($_SERVER));
623 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; 572 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
624 echo '<channel>'; 573 echo '<channel>';
625 echo '<title>Daily - '. $GLOBALS['title'] . '</title>'; 574 echo '<title>Daily - '. $conf->get('general.title') . '</title>';
626 echo '<link>'. $pageaddr .'</link>'; 575 echo '<link>'. $pageaddr .'</link>';
627 echo '<description>Daily shared links</description>'; 576 echo '<description>Daily shared links</description>';
628 echo '<language>en-en</language>'; 577 echo '<language>en-en</language>';
@@ -641,8 +590,8 @@ function showDailyRSS() {
641 // We pre-format some fields for proper output. 590 // We pre-format some fields for proper output.
642 foreach ($linkdates as $linkdate) { 591 foreach ($linkdates as $linkdate) {
643 $l = $LINKSDB[$linkdate]; 592 $l = $LINKSDB[$linkdate];
644 $l['formatedDescription'] = format_description($l['description'], $GLOBALS['redirector']); 593 $l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url'));
645 $l['thumbnail'] = thumbnail($l['url']); 594 $l['thumbnail'] = thumbnail($conf, $l['url']);
646 $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']); 595 $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']);
647 $l['timestamp'] = $l_date->getTimestamp(); 596 $l['timestamp'] = $l_date->getTimestamp();
648 if (startsWith($l['url'], '?')) { 597 if (startsWith($l['url'], '?')) {
@@ -653,11 +602,12 @@ function showDailyRSS() {
653 602
654 // Then build the HTML for this day: 603 // Then build the HTML for this day:
655 $tpl = new RainTPL; 604 $tpl = new RainTPL;
656 $tpl->assign('title', $GLOBALS['title']); 605 $tpl->assign('title', $conf->get('general.title'));
657 $tpl->assign('daydate', $dayDate->getTimestamp()); 606 $tpl->assign('daydate', $dayDate->getTimestamp());
658 $tpl->assign('absurl', $absurl); 607 $tpl->assign('absurl', $absurl);
659 $tpl->assign('links', $links); 608 $tpl->assign('links', $links);
660 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); 609 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS)));
610 $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false));
661 $html = $tpl->draw('dailyrss', $return_string=true); 611 $html = $tpl->draw('dailyrss', $return_string=true);
662 612
663 echo $html . PHP_EOL; 613 echo $html . PHP_EOL;
@@ -672,12 +622,14 @@ function showDailyRSS() {
672/** 622/**
673 * Show the 'Daily' page. 623 * Show the 'Daily' page.
674 * 624 *
675 * @param PageBuilder $pageBuilder Template engine wrapper. 625 * @param PageBuilder $pageBuilder Template engine wrapper.
676 * @param LinkDB $LINKSDB LinkDB instance. 626 * @param LinkDB $LINKSDB LinkDB instance.
627 * @param ConfigManager $conf Configuration Manager instance.
628 * @param PluginManager $pluginManager Plugin Manager instane.
677 */ 629 */
678function showDaily($pageBuilder, $LINKSDB) 630function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
679{ 631{
680 $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 632 $day=date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
681 if (isset($_GET['day'])) $day=$_GET['day']; 633 if (isset($_GET['day'])) $day=$_GET['day'];
682 634
683 $days = $LINKSDB->days(); 635 $days = $LINKSDB->days();
@@ -705,8 +657,8 @@ function showDaily($pageBuilder, $LINKSDB)
705 $taglist = explode(' ',$link['tags']); 657 $taglist = explode(' ',$link['tags']);
706 uasort($taglist, 'strcasecmp'); 658 uasort($taglist, 'strcasecmp');
707 $linksToDisplay[$key]['taglist']=$taglist; 659 $linksToDisplay[$key]['taglist']=$taglist;
708 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $GLOBALS['redirector']); 660 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
709 $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']); 661 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
710 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 662 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
711 $linksToDisplay[$key]['timestamp'] = $date->getTimestamp(); 663 $linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
712 } 664 }
@@ -741,7 +693,7 @@ function showDaily($pageBuilder, $LINKSDB)
741 'previousday' => $previousday, 693 'previousday' => $previousday,
742 'nextday' => $nextday, 694 'nextday' => $nextday,
743 ); 695 );
744 $pluginManager = PluginManager::getInstance(); 696
745 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn())); 697 $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn()));
746 698
747 foreach ($data as $key => $value) { 699 foreach ($data as $key => $value) {
@@ -752,36 +704,46 @@ function showDaily($pageBuilder, $LINKSDB)
752 exit; 704 exit;
753} 705}
754 706
755// Renders the linklist 707/**
756function showLinkList($PAGE, $LINKSDB) { 708 * Renders the linklist
757 buildLinkList($PAGE,$LINKSDB); // Compute list of links to display 709 *
710 * @param pageBuilder $PAGE pageBuilder instance.
711 * @param LinkDB $LINKSDB LinkDB instance.
712 * @param ConfigManager $conf Configuration Manager instance.
713 * @param PluginManager $pluginManager Plugin Manager instance.
714 */
715function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) {
716 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager); // Compute list of links to display
758 $PAGE->renderPage('linklist'); 717 $PAGE->renderPage('linklist');
759} 718}
760 719
761 720/**
762// ------------------------------------------------------------------------------------------ 721 * Render HTML page (according to URL parameters and user rights)
763// Render HTML page (according to URL parameters and user rights) 722 *
764function renderPage() 723 * @param ConfigManager $conf Configuration Manager instance.
724 * @param PluginManager $pluginManager Plugin Manager instance,
725 */
726function renderPage($conf, $pluginManager)
765{ 727{
766 $LINKSDB = new LinkDB( 728 $LINKSDB = new LinkDB(
767 $GLOBALS['config']['DATASTORE'], 729 $conf->get('resource.datastore'),
768 isLoggedIn(), 730 isLoggedIn(),
769 $GLOBALS['config']['HIDE_PUBLIC_LINKS'], 731 $conf->get('privacy.hide_public_links'),
770 $GLOBALS['redirector'], 732 $conf->get('redirector.url'),
771 $GLOBALS['config']['REDIRECTOR_URLENCODE'] 733 $conf->get('redirector.encode_url')
772 ); 734 );
773 735
774 $updater = new Updater( 736 $updater = new Updater(
775 read_updates_file($GLOBALS['config']['UPDATES_FILE']), 737 read_updates_file($conf->get('resource.updates')),
776 $GLOBALS,
777 $LINKSDB, 738 $LINKSDB,
739 $conf,
778 isLoggedIn() 740 isLoggedIn()
779 ); 741 );
780 try { 742 try {
781 $newUpdates = $updater->update(); 743 $newUpdates = $updater->update();
782 if (! empty($newUpdates)) { 744 if (! empty($newUpdates)) {
783 write_updates_file( 745 write_updates_file(
784 $GLOBALS['config']['UPDATES_FILE'], 746 $conf->get('resource.updates'),
785 $updater->getDoneUpdates() 747 $updater->getDoneUpdates()
786 ); 748 );
787 } 749 }
@@ -790,7 +752,7 @@ function renderPage()
790 die($e->getMessage()); 752 die($e->getMessage());
791 } 753 }
792 754
793 $PAGE = new PageBuilder(); 755 $PAGE = new PageBuilder($conf);
794 $PAGE->assign('linkcount', count($LINKSDB)); 756 $PAGE->assign('linkcount', count($LINKSDB));
795 $PAGE->assign('privateLinkcount', count_private($LINKSDB)); 757 $PAGE->assign('privateLinkcount', count_private($LINKSDB));
796 758
@@ -805,7 +767,7 @@ function renderPage()
805 'header', 767 'header',
806 'footer', 768 'footer',
807 ); 769 );
808 $pluginManager = PluginManager::getInstance(); 770
809 foreach($common_hooks as $name) { 771 foreach($common_hooks as $name) {
810 $plugin_data = array(); 772 $plugin_data = array();
811 $pluginManager->executeHooks('render_' . $name, $plugin_data, 773 $pluginManager->executeHooks('render_' . $name, $plugin_data,
@@ -820,8 +782,8 @@ function renderPage()
820 // -------- Display login form. 782 // -------- Display login form.
821 if ($targetPage == Router::$PAGE_LOGIN) 783 if ($targetPage == Router::$PAGE_LOGIN)
822 { 784 {
823 if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli 785 if ($conf->get('security.open_shaarli')) { header('Location: ?'); exit; } // No need to login for open Shaarli
824 $token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful. 786 $token=''; if (ban_canLogin($conf)) $token=getToken($conf); // Do not waste token generation if not useful.
825 $PAGE->assign('token',$token); 787 $PAGE->assign('token',$token);
826 if (isset($_GET['username'])) { 788 if (isset($_GET['username'])) {
827 $PAGE->assign('username', escape($_GET['username'])); 789 $PAGE->assign('username', escape($_GET['username']));
@@ -833,7 +795,7 @@ function renderPage()
833 // -------- User wants to logout. 795 // -------- User wants to logout.
834 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) 796 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout'))
835 { 797 {
836 invalidateCaches($GLOBALS['config']['PAGECACHE']); 798 invalidateCaches($conf->get('resource.page_cache'));
837 logout(); 799 logout();
838 header('Location: ?'); 800 header('Location: ?');
839 exit; 801 exit;
@@ -850,7 +812,7 @@ function renderPage()
850 foreach($links as $link) 812 foreach($links as $link)
851 { 813 {
852 $permalink='?'.escape(smallhash($link['linkdate'])); 814 $permalink='?'.escape(smallhash($link['linkdate']));
853 $thumb=lazyThumbnail($link['url'],$permalink); 815 $thumb=lazyThumbnail($conf, $link['url'],$permalink);
854 if ($thumb!='') // Only output links which have a thumbnail. 816 if ($thumb!='') // Only output links which have a thumbnail.
855 { 817 {
856 $link['thumbnail']=$thumb; // Thumbnail HTML code. 818 $link['thumbnail']=$thumb; // Thumbnail HTML code.
@@ -922,7 +884,7 @@ function renderPage()
922 884
923 // Daily page. 885 // Daily page.
924 if ($targetPage == Router::$PAGE_DAILY) { 886 if ($targetPage == Router::$PAGE_DAILY) {
925 showDaily($PAGE, $LINKSDB); 887 showDaily($PAGE, $LINKSDB, $conf, $pluginManager);
926 } 888 }
927 889
928 // ATOM and RSS feed. 890 // ATOM and RSS feed.
@@ -933,7 +895,7 @@ function renderPage()
933 // Cache system 895 // Cache system
934 $query = $_SERVER['QUERY_STRING']; 896 $query = $_SERVER['QUERY_STRING'];
935 $cache = new CachedPage( 897 $cache = new CachedPage(
936 $GLOBALS['config']['PAGECACHE'], 898 $conf->get('resource.page_cache'),
937 page_url($_SERVER), 899 page_url($_SERVER),
938 startsWith($query,'do='. $targetPage) && !isLoggedIn() 900 startsWith($query,'do='. $targetPage) && !isLoggedIn()
939 ); 901 );
@@ -946,15 +908,15 @@ function renderPage()
946 // Generate data. 908 // Generate data.
947 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn()); 909 $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn());
948 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); 910 $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
949 $feedGenerator->setHideDates($GLOBALS['config']['HIDE_TIMESTAMPS'] && !isLoggedIn()); 911 $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn());
950 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']); 912 $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
951 if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) { 913 $pshUrl = $conf->get('config.PUBSUBHUB_URL');
952 $feedGenerator->setPubsubhubUrl($GLOBALS['config']['PUBSUBHUB_URL']); 914 if (!empty($pshUrl)) {
915 $feedGenerator->setPubsubhubUrl($pshUrl);
953 } 916 }
954 $data = $feedGenerator->buildData(); 917 $data = $feedGenerator->buildData();
955 918
956 // Process plugin hook. 919 // Process plugin hook.
957 $pluginManager = PluginManager::getInstance();
958 $pluginManager->executeHooks('render_feed', $data, array( 920 $pluginManager->executeHooks('render_feed', $data, array(
959 'loggedin' => isLoggedIn(), 921 'loggedin' => isLoggedIn(),
960 'target' => $targetPage, 922 'target' => $targetPage,
@@ -1080,7 +1042,7 @@ function renderPage()
1080 exit; 1042 exit;
1081 } 1043 }
1082 1044
1083 showLinkList($PAGE, $LINKSDB); 1045 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
1084 if (isset($_GET['edit_link'])) { 1046 if (isset($_GET['edit_link'])) {
1085 header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); 1047 header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
1086 exit; 1048 exit;
@@ -1110,19 +1072,23 @@ function renderPage()
1110 // -------- User wants to change his/her password. 1072 // -------- User wants to change his/her password.
1111 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) 1073 if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
1112 { 1074 {
1113 if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.'); 1075 if ($conf->get('security.open_shaarli')) {
1076 die('You are not supposed to change a password on an Open Shaarli.');
1077 }
1078
1114 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 1079 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
1115 { 1080 {
1116 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away! 1081 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1117 1082
1118 // Make sure old password is correct. 1083 // Make sure old password is correct.
1119 $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1084 $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt'));
1120 if ($oldhash!=$GLOBALS['hash']) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; } 1085 if ($oldhash!= $conf->get('credentials.hash')) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
1121 // Save new password 1086 // Save new password
1122 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1087 // Salt renders rainbow-tables attacks useless.
1123 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1088 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
1089 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt')));
1124 try { 1090 try {
1125 writeConfig($GLOBALS, isLoggedIn()); 1091 $conf->write(isLoggedIn());
1126 } 1092 }
1127 catch(Exception $e) { 1093 catch(Exception $e) {
1128 error_log( 1094 error_log(
@@ -1139,7 +1105,7 @@ function renderPage()
1139 } 1105 }
1140 else // show the change password form. 1106 else // show the change password form.
1141 { 1107 {
1142 $PAGE->assign('token',getToken()); 1108 $PAGE->assign('token',getToken($conf));
1143 $PAGE->renderPage('changepassword'); 1109 $PAGE->renderPage('changepassword');
1144 exit; 1110 exit;
1145 } 1111 }
@@ -1159,17 +1125,17 @@ function renderPage()
1159 ) { 1125 ) {
1160 $tz = $_POST['continent'] . '/' . $_POST['city']; 1126 $tz = $_POST['continent'] . '/' . $_POST['city'];
1161 } 1127 }
1162 $GLOBALS['timezone'] = $tz; 1128 $conf->set('general.timezone', $tz);
1163 $GLOBALS['title']=$_POST['title']; 1129 $conf->set('general.title', escape($_POST['title']));
1164 $GLOBALS['titleLink']=$_POST['titleLink']; 1130 $conf->set('general.header_link', escape($_POST['titleLink']));
1165 $GLOBALS['redirector']=$_POST['redirector']; 1131 $conf->set('redirector.url', escape($_POST['redirector']));
1166 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']); 1132 $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection']));
1167 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']); 1133 $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault']));
1168 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']); 1134 $conf->set('feed.rss_permalinks', !empty($_POST['enableRssPermalinks']));
1169 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1135 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
1170 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']); 1136 $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks']));
1171 try { 1137 try {
1172 writeConfig($GLOBALS, isLoggedIn()); 1138 $conf->write(isLoggedIn());
1173 } 1139 }
1174 catch(Exception $e) { 1140 catch(Exception $e) {
1175 error_log( 1141 error_log(
@@ -1178,20 +1144,24 @@ function renderPage()
1178 ); 1144 );
1179 1145
1180 // TODO: do not handle exceptions/errors in JS. 1146 // TODO: do not handle exceptions/errors in JS.
1181 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>'; 1147 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=configure\';</script>';
1182 exit; 1148 exit;
1183 } 1149 }
1184 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; 1150 echo '<script>alert("Configuration was saved.");document.location=\'?do=configure\';</script>';
1185 exit; 1151 exit;
1186 } 1152 }
1187 else // Show the configuration form. 1153 else // Show the configuration form.
1188 { 1154 {
1189 $PAGE->assign('token',getToken()); 1155 $PAGE->assign('token',getToken($conf));
1190 $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] ); 1156 $PAGE->assign('title', $conf->get('general.title'));
1191 $PAGE->assign('redirector', empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] ); 1157 $PAGE->assign('redirector', $conf->get('redirector.url'));
1192 list($timezone_form, $timezone_js) = generateTimeZoneForm($GLOBALS['timezone']); 1158 list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
1193 $PAGE->assign('timezone_form', $timezone_form); 1159 $PAGE->assign('timezone_form', $timezone_form);
1194 $PAGE->assign('timezone_js',$timezone_js); 1160 $PAGE->assign('timezone_js',$timezone_js);
1161 $PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
1162 $PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
1163 $PAGE->assign('enable_update_check', $conf->get('updates.check_updates', true));
1164 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
1195 $PAGE->renderPage('configure'); 1165 $PAGE->renderPage('configure');
1196 exit; 1166 exit;
1197 } 1167 }
@@ -1201,7 +1171,7 @@ function renderPage()
1201 if ($targetPage == Router::$PAGE_CHANGETAG) 1171 if ($targetPage == Router::$PAGE_CHANGETAG)
1202 { 1172 {
1203 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { 1173 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) {
1204 $PAGE->assign('token', getToken()); 1174 $PAGE->assign('token', getToken($conf));
1205 $PAGE->assign('tags', $LINKSDB->allTags()); 1175 $PAGE->assign('tags', $LINKSDB->allTags());
1206 $PAGE->renderPage('changetag'); 1176 $PAGE->renderPage('changetag');
1207 exit; 1177 exit;
@@ -1223,7 +1193,7 @@ function renderPage()
1223 $value['tags']=trim(implode(' ',$tags)); 1193 $value['tags']=trim(implode(' ',$tags));
1224 $LINKSDB[$key]=$value; 1194 $LINKSDB[$key]=$value;
1225 } 1195 }
1226 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); 1196 $LINKSDB->savedb($conf->get('resource.page_cache'));
1227 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>'; 1197 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
1228 exit; 1198 exit;
1229 } 1199 }
@@ -1240,7 +1210,7 @@ function renderPage()
1240 $value['tags']=trim(implode(' ',$tags)); 1210 $value['tags']=trim(implode(' ',$tags));
1241 $LINKSDB[$key]=$value; 1211 $LINKSDB[$key]=$value;
1242 } 1212 }
1243 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. 1213 $LINKSDB->savedb($conf->get('resource.page_cache')); // Save to disk.
1244 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>'; 1214 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
1245 exit; 1215 exit;
1246 } 1216 }
@@ -1291,8 +1261,8 @@ function renderPage()
1291 $pluginManager->executeHooks('save_link', $link); 1261 $pluginManager->executeHooks('save_link', $link);
1292 1262
1293 $LINKSDB[$linkdate] = $link; 1263 $LINKSDB[$linkdate] = $link;
1294 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); 1264 $LINKSDB->savedb($conf->get('resource.page_cache'));
1295 pubsubhub(); 1265 pubsubhub($conf);
1296 1266
1297 // If we are called from the bookmarklet, we must close the popup: 1267 // If we are called from the bookmarklet, we must close the popup:
1298 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { 1268 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
@@ -1333,7 +1303,7 @@ function renderPage()
1333 $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]); 1303 $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
1334 1304
1335 unset($LINKSDB[$linkdate]); 1305 unset($LINKSDB[$linkdate]);
1336 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk 1306 $LINKSDB->savedb('resource.page_cache'); // save to disk
1337 1307
1338 // If we are called from the bookmarklet, we must close the popup: 1308 // If we are called from the bookmarklet, we must close the popup:
1339 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1309 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1376,7 +1346,7 @@ function renderPage()
1376 $data = array( 1346 $data = array(
1377 'link' => $link, 1347 'link' => $link,
1378 'link_is_new' => false, 1348 'link_is_new' => false,
1379 'token' => getToken(), 1349 'token' => getToken($conf),
1380 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), 1350 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
1381 'tags' => $LINKSDB->allTags(), 1351 'tags' => $LINKSDB->allTags(),
1382 ); 1352 );
@@ -1443,10 +1413,11 @@ function renderPage()
1443 $data = array( 1413 $data = array(
1444 'link' => $link, 1414 'link' => $link,
1445 'link_is_new' => $link_is_new, 1415 'link_is_new' => $link_is_new,
1446 'token' => getToken(), // XSRF protection. 1416 'token' => getToken($conf), // XSRF protection.
1447 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), 1417 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
1448 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), 1418 'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
1449 'tags' => $LINKSDB->allTags(), 1419 'tags' => $LINKSDB->allTags(),
1420 'default_private_links' => $conf->get('default_private_links', false),
1450 ); 1421 );
1451 $pluginManager->executeHooks('render_editlink', $data); 1422 $pluginManager->executeHooks('render_editlink', $data);
1452 1423
@@ -1520,7 +1491,7 @@ function renderPage()
1520 // -------- Show upload/import dialog: 1491 // -------- Show upload/import dialog:
1521 if ($targetPage == Router::$PAGE_IMPORT) 1492 if ($targetPage == Router::$PAGE_IMPORT)
1522 { 1493 {
1523 $PAGE->assign('token',getToken()); 1494 $PAGE->assign('token',getToken($conf));
1524 $PAGE->assign('maxfilesize',getMaxFileSize()); 1495 $PAGE->assign('maxfilesize',getMaxFileSize());
1525 $PAGE->renderPage('import'); 1496 $PAGE->renderPage('import');
1526 exit; 1497 exit;
@@ -1533,7 +1504,7 @@ function renderPage()
1533 // Split plugins into 2 arrays: ordered enabled plugins and disabled. 1504 // Split plugins into 2 arrays: ordered enabled plugins and disabled.
1534 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); 1505 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; });
1535 // Load parameters. 1506 // Load parameters.
1536 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $GLOBALS['plugins']); 1507 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array()));
1537 uasort( 1508 uasort(
1538 $enabledPlugins, 1509 $enabledPlugins,
1539 function($a, $b) { return $a['order'] - $b['order']; } 1510 function($a, $b) { return $a['order'] - $b['order']; }
@@ -1552,13 +1523,13 @@ function renderPage()
1552 if (isset($_POST['parameters_form'])) { 1523 if (isset($_POST['parameters_form'])) {
1553 unset($_POST['parameters_form']); 1524 unset($_POST['parameters_form']);
1554 foreach ($_POST as $param => $value) { 1525 foreach ($_POST as $param => $value) {
1555 $GLOBALS['plugins'][$param] = escape($value); 1526 $conf->set('plugins.'. $param, escape($value));
1556 } 1527 }
1557 } 1528 }
1558 else { 1529 else {
1559 $GLOBALS['config']['ENABLED_PLUGINS'] = save_plugin_config($_POST); 1530 $conf->set('general.enabled_plugins', save_plugin_config($_POST));
1560 } 1531 }
1561 writeConfig($GLOBALS, isLoggedIn()); 1532 $conf->write(isLoggedIn());
1562 } 1533 }
1563 catch (Exception $e) { 1534 catch (Exception $e) {
1564 error_log( 1535 error_log(
@@ -1575,13 +1546,17 @@ function renderPage()
1575 } 1546 }
1576 1547
1577 // -------- Otherwise, simply display search form and links: 1548 // -------- Otherwise, simply display search form and links:
1578 showLinkList($PAGE, $LINKSDB); 1549 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
1579 exit; 1550 exit;
1580} 1551}
1581 1552
1582// ----------------------------------------------------------------------------------------------- 1553/**
1583// Process the import file form. 1554 * Process the import file form.
1584function importFile($LINKSDB) 1555 *
1556 * @param LinkDB $LINKSDB Loaded LinkDB instance.
1557 * @param ConfigManager $conf Configuration Manager instance.
1558 */
1559function importFile($LINKSDB, $conf)
1585{ 1560{
1586 if (!isLoggedIn()) { die('Not allowed.'); } 1561 if (!isLoggedIn()) { die('Not allowed.'); }
1587 1562
@@ -1654,7 +1629,7 @@ function importFile($LINKSDB)
1654 } 1629 }
1655 } 1630 }
1656 } 1631 }
1657 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); 1632 $LINKSDB->savedb($conf->get('resource.page_cache'));
1658 1633
1659 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>'; 1634 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
1660 } 1635 }
@@ -1668,10 +1643,12 @@ function importFile($LINKSDB)
1668 * Template for the list of links (<div id="linklist">) 1643 * Template for the list of links (<div id="linklist">)
1669 * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' 1644 * This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
1670 * 1645 *
1671 * @param pageBuilder $PAGE pageBuilder instance. 1646 * @param pageBuilder $PAGE pageBuilder instance.
1672 * @param LinkDB $LINKSDB LinkDB instance. 1647 * @param LinkDB $LINKSDB LinkDB instance.
1648 * @param ConfigManager $conf Configuration Manager instance.
1649 * @param PluginManager $pluginManager Plugin Manager instance.
1673 */ 1650 */
1674function buildLinkList($PAGE,$LINKSDB) 1651function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
1675{ 1652{
1676 // Used in templates 1653 // Used in templates
1677 $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; 1654 $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
@@ -1700,7 +1677,7 @@ function buildLinkList($PAGE,$LINKSDB)
1700 1677
1701 // If there is only a single link, we change on-the-fly the title of the page. 1678 // If there is only a single link, we change on-the-fly the title of the page.
1702 if (count($linksToDisplay) == 1) { 1679 if (count($linksToDisplay) == 1) {
1703 $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title']; 1680 $conf->set('pagetitle', $linksToDisplay[$keys[0]]['title'] .' - '. $conf->get('general.title'));
1704 } 1681 }
1705 1682
1706 // Select articles according to paging. 1683 // Select articles according to paging.
@@ -1716,7 +1693,7 @@ function buildLinkList($PAGE,$LINKSDB)
1716 while ($i<$end && $i<count($keys)) 1693 while ($i<$end && $i<count($keys))
1717 { 1694 {
1718 $link = $linksToDisplay[$keys[$i]]; 1695 $link = $linksToDisplay[$keys[$i]];
1719 $link['description'] = format_description($link['description'], $GLOBALS['redirector']); 1696 $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
1720 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; 1697 $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
1721 $link['class'] = $link['private'] == 0 ? $classLi : 'private'; 1698 $link['class'] = $link['private'] == 0 ? $classLi : 'private';
1722 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); 1699 $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
@@ -1747,7 +1724,7 @@ function buildLinkList($PAGE,$LINKSDB)
1747 $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl; 1724 $next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl;
1748 } 1725 }
1749 1726
1750 $token = isLoggedIn() ? getToken() : ''; 1727 $token = isLoggedIn() ? getToken($conf) : '';
1751 1728
1752 // Fill all template fields. 1729 // Fill all template fields.
1753 $data = array( 1730 $data = array(
@@ -1758,17 +1735,16 @@ function buildLinkList($PAGE,$LINKSDB)
1758 'result_count' => count($linksToDisplay), 1735 'result_count' => count($linksToDisplay),
1759 'search_term' => $searchterm, 1736 'search_term' => $searchterm,
1760 'search_tags' => $searchtags, 1737 'search_tags' => $searchtags,
1761 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL. 1738 'redirector' => $conf->get('redirector.url'), // Optional redirector URL.
1762 'token' => $token, 1739 'token' => $token,
1763 'links' => $linkDisp, 1740 'links' => $linkDisp,
1764 'tags' => $LINKSDB->allTags(), 1741 'tags' => $LINKSDB->allTags(),
1765 ); 1742 );
1766 // FIXME! temporary fix - see #399. 1743 // FIXME! temporary fix - see #399.
1767 if (!empty($GLOBALS['pagetitle']) && count($linkDisp) == 1) { 1744 if ($conf->exists('pagetitle') && count($linkDisp) == 1) {
1768 $data['pagetitle'] = $GLOBALS['pagetitle']; 1745 $data['pagetitle'] = $conf->get('pagetitle');
1769 } 1746 }
1770 1747
1771 $pluginManager = PluginManager::getInstance();
1772 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); 1748 $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn()));
1773 1749
1774 foreach ($data as $key => $value) { 1750 foreach ($data as $key => $value) {
@@ -1778,18 +1754,26 @@ function buildLinkList($PAGE,$LINKSDB)
1778 return; 1754 return;
1779} 1755}
1780 1756
1781// Compute the thumbnail for a link. 1757/**
1782// 1758 * Compute the thumbnail for a link.
1783// With a link to the original URL. 1759 *
1784// Understands various services (youtube.com...) 1760 * With a link to the original URL.
1785// Input: $url = URL for which the thumbnail must be found. 1761 * Understands various services (youtube.com...)
1786// $href = if provided, this URL will be followed instead of $url 1762 * Input: $url = URL for which the thumbnail must be found.
1787// Returns an associative array with thumbnail attributes (src,href,width,height,style,alt) 1763 * $href = if provided, this URL will be followed instead of $url
1788// Some of them may be missing. 1764 * Returns an associative array with thumbnail attributes (src,href,width,height,style,alt)
1789// Return an empty array if no thumbnail available. 1765 * Some of them may be missing.
1790function computeThumbnail($url,$href=false) 1766 * Return an empty array if no thumbnail available.
1767 *
1768 * @param ConfigManager $conf Configuration Manager instance.
1769 * @param string $url
1770 * @param string|bool $href
1771 *
1772 * @return array
1773 */
1774function computeThumbnail($conf, $url, $href = false)
1791{ 1775{
1792 if (!$GLOBALS['config']['ENABLE_THUMBNAILS']) return array(); 1776 if (!$conf->get('thumbnail.enable_thumbnails')) return array();
1793 if ($href==false) $href=$url; 1777 if ($href==false) $href=$url;
1794 1778
1795 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. 1779 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
@@ -1857,7 +1841,7 @@ function computeThumbnail($url,$href=false)
1857 // So we deport the thumbnail generation in order not to slow down page generation 1841 // So we deport the thumbnail generation in order not to slow down page generation
1858 // (and we also cache the thumbnail) 1842 // (and we also cache the thumbnail)
1859 1843
1860 if (!$GLOBALS['config']['ENABLE_LOCALCACHE']) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache. 1844 if (! $conf->get('thumbnail.enable_localcache')) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache.
1861 1845
1862 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') 1846 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')
1863 || $domain=='vimeo.com' 1847 || $domain=='vimeo.com'
@@ -1880,7 +1864,7 @@ function computeThumbnail($url,$href=false)
1880 $path = parse_url($url,PHP_URL_PATH); 1864 $path = parse_url($url,PHP_URL_PATH);
1881 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL. 1865 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
1882 } 1866 }
1883 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) 1867 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1884 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url), 1868 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1885 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail'); 1869 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1886 } 1870 }
@@ -1891,7 +1875,7 @@ function computeThumbnail($url,$href=false)
1891 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION)); 1875 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
1892 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif') 1876 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
1893 { 1877 {
1894 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) 1878 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1895 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url), 1879 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1896 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail'); 1880 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1897 } 1881 }
@@ -1908,7 +1892,9 @@ function computeThumbnail($url,$href=false)
1908// Returns '' if no thumbnail available. 1892// Returns '' if no thumbnail available.
1909function thumbnail($url,$href=false) 1893function thumbnail($url,$href=false)
1910{ 1894{
1911 $t = computeThumbnail($url,$href); 1895 // FIXME!
1896 global $conf;
1897 $t = computeThumbnail($conf, $url,$href);
1912 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL. 1898 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1913 1899
1914 $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"'; 1900 $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"';
@@ -1926,9 +1912,11 @@ function thumbnail($url,$href=false)
1926// Input: $url = URL for which the thumbnail must be found. 1912// Input: $url = URL for which the thumbnail must be found.
1927// $href = if provided, this URL will be followed instead of $url 1913// $href = if provided, this URL will be followed instead of $url
1928// Returns '' if no thumbnail available. 1914// Returns '' if no thumbnail available.
1929function lazyThumbnail($url,$href=false) 1915function lazyThumbnail($conf, $url,$href=false)
1930{ 1916{
1931 $t = computeThumbnail($url,$href); 1917 // FIXME!
1918 global $conf;
1919 $t = computeThumbnail($conf, $url,$href);
1932 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL. 1920 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1933 1921
1934 $html='<a href="'.escape($t['href']).'">'; 1922 $html='<a href="'.escape($t['href']).'">';
@@ -1954,10 +1942,13 @@ function lazyThumbnail($url,$href=false)
1954} 1942}
1955 1943
1956 1944
1957// ----------------------------------------------------------------------------------------------- 1945/**
1958// Installation 1946 * Installation
1959// This function should NEVER be called if the file data/config.php exists. 1947 * This function should NEVER be called if the file data/config.php exists.
1960function install() 1948 *
1949 * @param ConfigManager $conf Configuration Manager instance.
1950 */
1951function install($conf)
1961{ 1952{
1962 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. 1953 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
1963 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); 1954 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
@@ -1994,15 +1985,21 @@ function install()
1994 ) { 1985 ) {
1995 $tz = $_POST['continent'].'/'.$_POST['city']; 1986 $tz = $_POST['continent'].'/'.$_POST['city'];
1996 } 1987 }
1997 $GLOBALS['timezone'] = $tz; 1988 $conf->set('general.timezone', $tz);
1998 // Everything is ok, let's create config file. 1989 $login = $_POST['setlogin'];
1999 $GLOBALS['login'] = $_POST['setlogin']; 1990 $conf->set('credentials.login', $login);
2000 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1991 $salt = sha1(uniqid('', true) .'_'. mt_rand());
2001 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1992 $conf->set('credentials.salt', $salt);
2002 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(index_url($_SERVER)) : $_POST['title'] ); 1993 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt));
2003 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1994 if (!empty($_POST['title'])) {
1995 $conf->set('general.title', escape($_POST['title']));
1996 } else {
1997 $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
1998 }
1999 $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
2004 try { 2000 try {
2005 writeConfig($GLOBALS, isLoggedIn()); 2001 // Everything is ok, let's create config file.
2002 $conf->write(isLoggedIn());
2006 } 2003 }
2007 catch(Exception $e) { 2004 catch(Exception $e) {
2008 error_log( 2005 error_log(
@@ -2025,42 +2022,46 @@ function install()
2025 $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>'; 2022 $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
2026 } 2023 }
2027 2024
2028 $PAGE = new PageBuilder(); 2025 $PAGE = new PageBuilder($conf);
2029 $PAGE->assign('timezone_html',$timezone_html); 2026 $PAGE->assign('timezone_html',$timezone_html);
2030 $PAGE->assign('timezone_js',$timezone_js); 2027 $PAGE->assign('timezone_js',$timezone_js);
2031 $PAGE->renderPage('install'); 2028 $PAGE->renderPage('install');
2032 exit; 2029 exit;
2033} 2030}
2034 2031
2035/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL, 2032/**
2036 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 2033 * Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
2037 The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail. 2034 * I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
2038 This function is called by passing the URL: 2035 * The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail.
2039 http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL] 2036 * This function is called by passing the URL:
2040 [URL] is the URL of the link (e.g. a flickr page) 2037 * http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL]
2041 [HMAC] is the signature for the [URL] (so that these URL cannot be forged). 2038 * [URL] is the URL of the link (e.g. a flickr page)
2042 The function below will fetch the image from the webservice and store it in the cache. 2039 * [HMAC] is the signature for the [URL] (so that these URL cannot be forged).
2043*/ 2040 * The function below will fetch the image from the webservice and store it in the cache.
2044function genThumbnail() 2041 *
2042 * @param ConfigManager $conf Configuration Manager instance,
2043 */
2044function genThumbnail($conf)
2045{ 2045{
2046 // Make sure the parameters in the URL were generated by us. 2046 // Make sure the parameters in the URL were generated by us.
2047 $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']); 2047 $sign = hash_hmac('sha256', $_GET['url'], $conf->get('credentials.salt'));
2048 if ($sign!=$_GET['hmac']) die('Naughty boy!'); 2048 if ($sign!=$_GET['hmac']) die('Naughty boy!');
2049 2049
2050 $cacheDir = $conf->get('resource.thumbnails_cache', 'cache');
2050 // Let's see if we don't already have the image for this URL in the cache. 2051 // Let's see if we don't already have the image for this URL in the cache.
2051 $thumbname=hash('sha1',$_GET['url']).'.jpg'; 2052 $thumbname=hash('sha1',$_GET['url']).'.jpg';
2052 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$thumbname)) 2053 if (is_file($cacheDir .'/'. $thumbname))
2053 { // We have the thumbnail, just serve it: 2054 { // We have the thumbnail, just serve it:
2054 header('Content-Type: image/jpeg'); 2055 header('Content-Type: image/jpeg');
2055 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname); 2056 echo file_get_contents($cacheDir .'/'. $thumbname);
2056 return; 2057 return;
2057 } 2058 }
2058 // We may also serve a blank image (if service did not respond) 2059 // We may also serve a blank image (if service did not respond)
2059 $blankname=hash('sha1',$_GET['url']).'.gif'; 2060 $blankname=hash('sha1',$_GET['url']).'.gif';
2060 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$blankname)) 2061 if (is_file($cacheDir .'/'. $blankname))
2061 { 2062 {
2062 header('Content-Type: image/gif'); 2063 header('Content-Type: image/gif');
2063 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname); 2064 echo file_get_contents($cacheDir .'/'. $blankname);
2064 return; 2065 return;
2065 } 2066 }
2066 2067
@@ -2107,7 +2108,7 @@ function genThumbnail()
2107 list($headers, $content) = get_http_response($imageurl, 10); 2108 list($headers, $content) = get_http_response($imageurl, 10);
2108 if (strpos($headers[0], '200 OK') !== false) { 2109 if (strpos($headers[0], '200 OK') !== false) {
2109 // Save image to cache. 2110 // Save image to cache.
2110 file_put_contents($GLOBALS['config']['CACHEDIR'].'/' . $thumbname, $content); 2111 file_put_contents($cacheDir .'/'. $thumbname, $content);
2111 header('Content-Type: image/jpeg'); 2112 header('Content-Type: image/jpeg');
2112 echo $content; 2113 echo $content;
2113 return; 2114 return;
@@ -2128,7 +2129,7 @@ function genThumbnail()
2128 list($headers, $content) = get_http_response($imageurl, 10); 2129 list($headers, $content) = get_http_response($imageurl, 10);
2129 if (strpos($headers[0], '200 OK') !== false) { 2130 if (strpos($headers[0], '200 OK') !== false) {
2130 // Save image to cache. 2131 // Save image to cache.
2131 file_put_contents($GLOBALS['config']['CACHEDIR'] . '/' . $thumbname, $content); 2132 file_put_contents($cacheDir .'/'. $thumbname, $content);
2132 header('Content-Type: image/jpeg'); 2133 header('Content-Type: image/jpeg');
2133 echo $content; 2134 echo $content;
2134 return; 2135 return;
@@ -2151,7 +2152,7 @@ function genThumbnail()
2151 // No control on image size, so wait long enough 2152 // No control on image size, so wait long enough
2152 list($headers, $content) = get_http_response($imageurl, 20); 2153 list($headers, $content) = get_http_response($imageurl, 20);
2153 if (strpos($headers[0], '200 OK') !== false) { 2154 if (strpos($headers[0], '200 OK') !== false) {
2154 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2155 $filepath = $cacheDir .'/'. $thumbname;
2155 file_put_contents($filepath, $content); // Save image to cache. 2156 file_put_contents($filepath, $content); // Save image to cache.
2156 if (resizeImage($filepath)) 2157 if (resizeImage($filepath))
2157 { 2158 {
@@ -2179,7 +2180,7 @@ function genThumbnail()
2179 // No control on image size, so wait long enough 2180 // No control on image size, so wait long enough
2180 list($headers, $content) = get_http_response($imageurl, 20); 2181 list($headers, $content) = get_http_response($imageurl, 20);
2181 if (strpos($headers[0], '200 OK') !== false) { 2182 if (strpos($headers[0], '200 OK') !== false) {
2182 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2183 $filepath = $cacheDir.'/'.$thumbname;
2183 // Save image to cache. 2184 // Save image to cache.
2184 file_put_contents($filepath, $content); 2185 file_put_contents($filepath, $content);
2185 if (resizeImage($filepath)) 2186 if (resizeImage($filepath))
@@ -2199,7 +2200,7 @@ function genThumbnail()
2199 // We allow 30 seconds max to download (and downloads are limited to 4 Mb) 2200 // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
2200 list($headers, $content) = get_http_response($url, 30); 2201 list($headers, $content) = get_http_response($url, 30);
2201 if (strpos($headers[0], '200 OK') !== false) { 2202 if (strpos($headers[0], '200 OK') !== false) {
2202 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname; 2203 $filepath = $cacheDir .'/'.$thumbname;
2203 // Save image to cache. 2204 // Save image to cache.
2204 file_put_contents($filepath, $content); 2205 file_put_contents($filepath, $content);
2205 if (resizeImage($filepath)) 2206 if (resizeImage($filepath))
@@ -2214,7 +2215,8 @@ function genThumbnail()
2214 2215
2215 // Otherwise, return an empty image (8x8 transparent gif) 2216 // Otherwise, return an empty image (8x8 transparent gif)
2216 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7'); 2217 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
2217 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname,$blankgif); // Also put something in cache so that this URL is not requested twice. 2218 // Also put something in cache so that this URL is not requested twice.
2219 file_put_contents($cacheDir .'/'. $blankname, $blankgif);
2218 header('Content-Type: image/gif'); 2220 header('Content-Type: image/gif');
2219 echo $blankgif; 2221 echo $blankgif;
2220} 2222}
@@ -2252,8 +2254,9 @@ function resizeImage($filepath)
2252 return true; 2254 return true;
2253} 2255}
2254 2256
2255if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. 2257if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail($conf); exit; } // Thumbnail generation/cache does not need the link database.
2256if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS(); exit; } 2258if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS($conf); exit; }
2257if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE']; 2259if (!isset($_SESSION['LINKS_PER_PAGE'])) {
2258renderPage(); 2260 $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);
2259?> 2261}
2262renderPage($conf, $pluginManager);
diff --git a/plugins/readityourself/config.php.dist b/plugins/readityourself/config.php.dist
deleted file mode 100644
index d6b5cb85..00000000
--- a/plugins/readityourself/config.php.dist
+++ /dev/null
@@ -1,3 +0,0 @@
1<?php
2
3$GLOBALS['plugins']['READITYOUSELF_URL'] = 'http://someurl.com'; \ No newline at end of file
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php
index c8df4c4f..4bfcf501 100644
--- a/plugins/readityourself/readityourself.php
+++ b/plugins/readityourself/readityourself.php
@@ -8,34 +8,31 @@
8// it seems kinda dead. 8// it seems kinda dead.
9// Not tested. 9// Not tested.
10 10
11// don't raise unnecessary warnings 11$riyUrl = $conf->get('plugins.READITYOUSELF_URL');
12if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) { 12if (empty($riyUrl)) {
13 include PluginManager::$PLUGINS_PATH . '/readityourself/config.php';
14}
15
16if (empty($GLOBALS['plugins']['READITYOUSELF_URL'])) {
17 $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '. 13 $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '.
18 'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '. 14 'Please define the "READITYOUSELF_URL" setting in the plugin administration page.';
19 'in "plugins/readityourself/config.php" or in your Shaarli config.php file.';
20} 15}
21 16
22/** 17/**
23 * Add readityourself icon to link_plugin when rendering linklist. 18 * Add readityourself icon to link_plugin when rendering linklist.
24 * 19 *
25 * @param mixed $data - linklist data. 20 * @param mixed $data Linklist data.
21 * @param ConfigManager $conf Configuration Manager instance.
26 * 22 *
27 * @return mixed - linklist data with readityourself plugin. 23 * @return mixed - linklist data with readityourself plugin.
28 */ 24 */
29function hook_readityourself_render_linklist($data) 25function hook_readityourself_render_linklist($data, $conf)
30{ 26{
31 if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) { 27 $riyUrl = $conf->get('plugins.READITYOUSELF_URL');
28 if (empty($riyUrl)) {
32 return $data; 29 return $data;
33 } 30 }
34 31
35 $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html'); 32 $readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
36 33
37 foreach ($data['links'] as &$value) { 34 foreach ($data['links'] as &$value) {
38 $readityourself = sprintf($readityourself_html, $GLOBALS['plugins']['READITYOUSELF_URL'], $value['url'], PluginManager::$PLUGINS_PATH); 35 $readityourself = sprintf($readityourself_html, $riyUrl, $value['url'], PluginManager::$PLUGINS_PATH);
39 $value['link_plugin'][] = $readityourself; 36 $value['link_plugin'][] = $readityourself;
40 } 37 }
41 38
diff --git a/plugins/wallabag/README.md b/plugins/wallabag/README.md
index 5bc35be1..3f930564 100644
--- a/plugins/wallabag/README.md
+++ b/plugins/wallabag/README.md
@@ -12,31 +12,26 @@ The directory structure should look like:
12 └── plugins 12 └── plugins
13    └── wallabag 13    └── wallabag
14    ├── README.md 14    ├── README.md
15 ├── config.php.dist
16    ├── wallabag.html 15    ├── wallabag.html
16    ├── wallabag.meta
17    ├── wallabag.php 17    ├── wallabag.php
18    └── wallabag.png 18    ├── wallabag.php
19    └── WallabagInstance.php
19``` 20```
20 21
21To enable the plugin, add `'wallabag'` to your list of enabled plugins in `data/options.php` (`PLUGINS` array). 22To enable the plugin, you can either:
22This should look like:
23 23
24``` 24 * enable it in the plugins administration page (`?do=pluginadmin`).
25$GLOBALS['config']['PLUGINS'] = array('qrcode', 'any_other_plugin', 'wallabag') 25 * add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section).
26```
27 26
28### Configuration 27### Configuration
29 28
30Copy `config.php.dist` into `config.php` and setup your instance. 29Go to the plugin administration page, and edit the following settings (with the plugin enabled).
31 30
32*Wallabag instance URL* 31**WALLABAG_URL**: *Wallabag instance URL*
33``` 32Example value: `http://v2.wallabag.org`
34$GLOBALS['config']['WALLABAG_URL'] = 'http://v2.wallabag.org' ;
35```
36 33
37*Wallabag version*: either `1` (for 1.x) or `2` (for 2.x) 34**WALLABAG_VERSION**: *Wallabag version*
38``` 35Value: either `1` (for 1.x) or `2` (for 2.x)
39$GLOBALS['config']['WALLABAG_VERSION'] = 2;
40```
41 36
42> Note: these settings can also be set in `data/config.php`. \ No newline at end of file 37> Note: these settings can also be set in `data/config.json.php`, in the plugins section. \ No newline at end of file
diff --git a/plugins/wallabag/config.php.dist b/plugins/wallabag/config.php.dist
deleted file mode 100644
index a602708f..00000000
--- a/plugins/wallabag/config.php.dist
+++ /dev/null
@@ -1,4 +0,0 @@
1<?php
2
3$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org';
4$GLOBALS['plugins']['WALLABAG_VERSION'] = 1; \ No newline at end of file
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php
index 0d6fc66d..ec09c8a1 100644
--- a/plugins/wallabag/wallabag.php
+++ b/plugins/wallabag/wallabag.php
@@ -6,34 +6,29 @@
6 6
7require_once 'WallabagInstance.php'; 7require_once 'WallabagInstance.php';
8 8
9// don't raise unnecessary warnings 9$wallabagUrl = $conf->get('plugins.WALLABAG_URL');
10if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) { 10if (empty($wallabagUrl)) {
11 include PluginManager::$PLUGINS_PATH . '/wallabag/config.php';
12}
13
14if (empty($GLOBALS['plugins']['WALLABAG_URL'])) {
15 $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '. 11 $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '.
16 'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '. 12 'Please define the "WALLABAG_URL" setting in the plugin administration page.';
17 'in "plugins/wallabag/config.php" or in your Shaarli config.php file.';
18} 13}
19 14
20/** 15/**
21 * Add wallabag icon to link_plugin when rendering linklist. 16 * Add wallabag icon to link_plugin when rendering linklist.
22 * 17 *
23 * @param mixed $data - linklist data. 18 * @param mixed $data Linklist data.
19 * @param ConfigManager $conf Configuration Manager instance.
24 * 20 *
25 * @return mixed - linklist data with wallabag plugin. 21 * @return mixed - linklist data with wallabag plugin.
26 */ 22 */
27function hook_wallabag_render_linklist($data) 23function hook_wallabag_render_linklist($data, $conf)
28{ 24{
29 if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) { 25 $wallabagUrl = $conf->get('plugins.WALLABAG_URL');
26 if (empty($wallabagUrl)) {
30 return $data; 27 return $data;
31 } 28 }
32 29
33 $version = isset($GLOBALS['plugins']['WALLABAG_VERSION']) 30 $version = $conf->get('plugins.WALLABAG_VERSION');
34 ? $GLOBALS['plugins']['WALLABAG_VERSION'] 31 $wallabagInstance = new WallabagInstance($wallabagUrl, $version);
35 : '';
36 $wallabagInstance = new WallabagInstance($GLOBALS['plugins']['WALLABAG_URL'], $version);
37 32
38 $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html'); 33 $wallabagHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');
39 34
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php
index 6064357d..c37a94f0 100644
--- a/tests/ApplicationUtilsTest.php
+++ b/tests/ApplicationUtilsTest.php
@@ -3,6 +3,7 @@
3 * ApplicationUtils' tests 3 * ApplicationUtils' tests
4 */ 4 */
5 5
6require_once 'application/config/ConfigManager.php';
6require_once 'application/ApplicationUtils.php'; 7require_once 'application/ApplicationUtils.php';
7 8
8/** 9/**
@@ -59,7 +60,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
59 $testTimeout 60 $testTimeout
60 ) 61 )
61 ); 62 );
62 $this->assertRegexp( 63 $this->assertRegExp(
63 self::$versionPattern, 64 self::$versionPattern,
64 ApplicationUtils::getLatestGitVersionCode( 65 ApplicationUtils::getLatestGitVersionCode(
65 'https://raw.githubusercontent.com/shaarli/Shaarli/' 66 'https://raw.githubusercontent.com/shaarli/Shaarli/'
@@ -275,21 +276,21 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
275 */ 276 */
276 public function testCheckCurrentResourcePermissions() 277 public function testCheckCurrentResourcePermissions()
277 { 278 {
278 $config = array( 279 $conf = new ConfigManager('');
279 'CACHEDIR' => 'cache', 280 $conf->set('resource.thumbnails_cache', 'cache');
280 'CONFIG_FILE' => 'data/config.php', 281 $conf->set('resource.config', 'data/config.php');
281 'DATADIR' => 'data', 282 $conf->set('resource.data_dir', 'data');
282 'DATASTORE' => 'data/datastore.php', 283 $conf->set('resource.datastore', 'data/datastore.php');
283 'IPBANS_FILENAME' => 'data/ipbans.php', 284 $conf->set('resource.ban_file', 'data/ipbans.php');
284 'LOG_FILE' => 'data/log.txt', 285 $conf->set('resource.log', 'data/log.txt');
285 'PAGECACHE' => 'pagecache', 286 $conf->set('resource.page_cache', 'pagecache');
286 'RAINTPL_TMP' => 'tmp', 287 $conf->set('resource.raintpl_tmp', 'tmp');
287 'RAINTPL_TPL' => 'tpl', 288 $conf->set('resource.raintpl_tpl', 'tpl');
288 'UPDATECHECK_FILENAME' => 'data/lastupdatecheck.txt' 289 $conf->set('resource.update_check', 'data/lastupdatecheck.txt');
289 ); 290
290 $this->assertEquals( 291 $this->assertEquals(
291 array(), 292 array(),
292 ApplicationUtils::checkResourcePermissions($config) 293 ApplicationUtils::checkResourcePermissions($conf)
293 ); 294 );
294 } 295 }
295 296
@@ -298,18 +299,17 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
298 */ 299 */
299 public function testCheckCurrentResourcePermissionsErrors() 300 public function testCheckCurrentResourcePermissionsErrors()
300 { 301 {
301 $config = array( 302 $conf = new ConfigManager('');
302 'CACHEDIR' => 'null/cache', 303 $conf->set('resource.thumbnails_cache', 'null/cache');
303 'CONFIG_FILE' => 'null/data/config.php', 304 $conf->set('resource.config', 'null/data/config.php');
304 'DATADIR' => 'null/data', 305 $conf->set('resource.data_dir', 'null/data');
305 'DATASTORE' => 'null/data/store.php', 306 $conf->set('resource.datastore', 'null/data/store.php');
306 'IPBANS_FILENAME' => 'null/data/ipbans.php', 307 $conf->set('resource.ban_file', 'null/data/ipbans.php');
307 'LOG_FILE' => 'null/data/log.txt', 308 $conf->set('resource.log', 'null/data/log.txt');
308 'PAGECACHE' => 'null/pagecache', 309 $conf->set('resource.page_cache', 'null/pagecache');
309 'RAINTPL_TMP' => 'null/tmp', 310 $conf->set('resource.raintpl_tmp', 'null/tmp');
310 'RAINTPL_TPL' => 'null/tpl', 311 $conf->set('resource.raintpl_tpl', 'null/tpl');
311 'UPDATECHECK_FILENAME' => 'null/data/lastupdatecheck.txt' 312 $conf->set('resource.update_check', 'null/data/lastupdatecheck.txt');
312 );
313 $this->assertEquals( 313 $this->assertEquals(
314 array( 314 array(
315 '"null/tpl" directory is not readable', 315 '"null/tpl" directory is not readable',
@@ -322,7 +322,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
322 '"null/tmp" directory is not readable', 322 '"null/tmp" directory is not readable',
323 '"null/tmp" directory is not writable' 323 '"null/tmp" directory is not writable'
324 ), 324 ),
325 ApplicationUtils::checkResourcePermissions($config) 325 ApplicationUtils::checkResourcePermissions($conf)
326 ); 326 );
327 } 327 }
328} 328}
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
deleted file mode 100644
index 7200aae6..00000000
--- a/tests/ConfigTest.php
+++ /dev/null
@@ -1,244 +0,0 @@
1<?php
2/**
3 * Config' tests
4 */
5
6require_once 'application/Config.php';
7
8/**
9 * Unitary tests for Shaarli config related functions
10 */
11class ConfigTest extends PHPUnit_Framework_TestCase
12{
13 // Configuration input set.
14 private static $configFields;
15
16 /**
17 * Executed before each test.
18 */
19 public function setUp()
20 {
21 self::$configFields = array(
22 'login' => 'login',
23 'hash' => 'hash',
24 'salt' => 'salt',
25 'timezone' => 'Europe/Paris',
26 'title' => 'title',
27 'titleLink' => 'titleLink',
28 'redirector' => '',
29 'disablesessionprotection' => false,
30 'privateLinkByDefault' => false,
31 'config' => array(
32 'CONFIG_FILE' => 'tests/config.php',
33 'DATADIR' => 'tests',
34 'config1' => 'config1data',
35 'config2' => 'config2data',
36 )
37 );
38 }
39
40 /**
41 * Executed after each test.
42 *
43 * @return void
44 */
45 public function tearDown()
46 {
47 if (is_file(self::$configFields['config']['CONFIG_FILE'])) {
48 unlink(self::$configFields['config']['CONFIG_FILE']);
49 }
50 }
51
52 /**
53 * Test writeConfig function, valid use case, while being logged in.
54 */
55 public function testWriteConfig()
56 {
57 writeConfig(self::$configFields, true);
58
59 include self::$configFields['config']['CONFIG_FILE'];
60 $this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
61 $this->assertEquals(self::$configFields['hash'], $GLOBALS['hash']);
62 $this->assertEquals(self::$configFields['salt'], $GLOBALS['salt']);
63 $this->assertEquals(self::$configFields['timezone'], $GLOBALS['timezone']);
64 $this->assertEquals(self::$configFields['title'], $GLOBALS['title']);
65 $this->assertEquals(self::$configFields['titleLink'], $GLOBALS['titleLink']);
66 $this->assertEquals(self::$configFields['redirector'], $GLOBALS['redirector']);
67 $this->assertEquals(self::$configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
68 $this->assertEquals(self::$configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
69 $this->assertEquals(self::$configFields['config']['config1'], $GLOBALS['config']['config1']);
70 $this->assertEquals(self::$configFields['config']['config2'], $GLOBALS['config']['config2']);
71 }
72
73 /**
74 * Test writeConfig option while logged in:
75 * 1. init fields.
76 * 2. update fields, add new sub config, add new root config.
77 * 3. rewrite config.
78 * 4. check result.
79 */
80 public function testWriteConfigFieldUpdate()
81 {
82 writeConfig(self::$configFields, true);
83 self::$configFields['title'] = 'ok';
84 self::$configFields['config']['config1'] = 'ok';
85 self::$configFields['config']['config_new'] = 'ok';
86 self::$configFields['new'] = 'should not be saved';
87 writeConfig(self::$configFields, true);
88
89 include self::$configFields['config']['CONFIG_FILE'];
90 $this->assertEquals('ok', $GLOBALS['title']);
91 $this->assertEquals('ok', $GLOBALS['config']['config1']);
92 $this->assertEquals('ok', $GLOBALS['config']['config_new']);
93 $this->assertFalse(isset($GLOBALS['new']));
94 }
95
96 /**
97 * Test writeConfig function with an empty array.
98 *
99 * @expectedException MissingFieldConfigException
100 */
101 public function testWriteConfigEmpty()
102 {
103 writeConfig(array(), true);
104 }
105
106 /**
107 * Test writeConfig function with a missing mandatory field.
108 *
109 * @expectedException MissingFieldConfigException
110 */
111 public function testWriteConfigMissingField()
112 {
113 unset(self::$configFields['login']);
114 writeConfig(self::$configFields, true);
115 }
116
117 /**
118 * Test writeConfig function while being logged out, and there is no config file existing.
119 */
120 public function testWriteConfigLoggedOutNoFile()
121 {
122 writeConfig(self::$configFields, false);
123 }
124
125 /**
126 * Test writeConfig function while being logged out, and a config file already exists.
127 *
128 * @expectedException UnauthorizedConfigException
129 */
130 public function testWriteConfigLoggedOutWithFile()
131 {
132 file_put_contents(self::$configFields['config']['CONFIG_FILE'], '');
133 writeConfig(self::$configFields, false);
134 }
135
136 /**
137 * Test save_plugin_config with valid data.
138 *
139 * @throws PluginConfigOrderException
140 */
141 public function testSavePluginConfigValid()
142 {
143 $data = array(
144 'order_plugin1' => 2, // no plugin related
145 'plugin2' => 0, // new - at the end
146 'plugin3' => 0, // 2nd
147 'order_plugin3' => 8,
148 'plugin4' => 0, // 1st
149 'order_plugin4' => 5,
150 );
151
152 $expected = array(
153 'plugin3',
154 'plugin4',
155 'plugin2',
156 );
157
158 $out = save_plugin_config($data);
159 $this->assertEquals($expected, $out);
160 }
161
162 /**
163 * Test save_plugin_config with invalid data.
164 *
165 * @expectedException PluginConfigOrderException
166 */
167 public function testSavePluginConfigInvalid()
168 {
169 $data = array(
170 'plugin2' => 0,
171 'plugin3' => 0,
172 'order_plugin3' => 0,
173 'plugin4' => 0,
174 'order_plugin4' => 0,
175 );
176
177 save_plugin_config($data);
178 }
179
180 /**
181 * Test save_plugin_config without data.
182 */
183 public function testSavePluginConfigEmpty()
184 {
185 $this->assertEquals(array(), save_plugin_config(array()));
186 }
187
188 /**
189 * Test validate_plugin_order with valid data.
190 */
191 public function testValidatePluginOrderValid()
192 {
193 $data = array(
194 'order_plugin1' => 2,
195 'plugin2' => 0,
196 'plugin3' => 0,
197 'order_plugin3' => 1,
198 'plugin4' => 0,
199 'order_plugin4' => 5,
200 );
201
202 $this->assertTrue(validate_plugin_order($data));
203 }
204
205 /**
206 * Test validate_plugin_order with invalid data.
207 */
208 public function testValidatePluginOrderInvalid()
209 {
210 $data = array(
211 'order_plugin1' => 2,
212 'order_plugin3' => 1,
213 'order_plugin4' => 1,
214 );
215
216 $this->assertFalse(validate_plugin_order($data));
217 }
218
219 /**
220 * Test load_plugin_parameter_values.
221 */
222 public function testLoadPluginParameterValues()
223 {
224 $plugins = array(
225 'plugin_name' => array(
226 'parameters' => array(
227 'param1' => true,
228 'param2' => false,
229 'param3' => '',
230 )
231 )
232 );
233
234 $parameters = array(
235 'param1' => 'value1',
236 'param2' => 'value2',
237 );
238
239 $result = load_plugin_parameter_values($plugins, $parameters);
240 $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
241 $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
242 $this->assertEquals('', $result['plugin_name']['parameters']['param3']);
243 }
244}
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index 647b2db2..460fb0c5 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -76,7 +76,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
76 // Test headers (RSS) 76 // Test headers (RSS)
77 $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); 77 $this->assertEquals(self::$RSS_LANGUAGE, $data['language']);
78 $this->assertEmpty($data['pubsubhub_url']); 78 $this->assertEmpty($data['pubsubhub_url']);
79 $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']); 79 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $data['last_update']);
80 $this->assertEquals(true, $data['show_dates']); 80 $this->assertEquals(true, $data['show_dates']);
81 $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); 81 $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']);
82 $this->assertEquals('http://host.tld/', $data['index_url']); 82 $this->assertEquals('http://host.tld/', $data['index_url']);
@@ -88,7 +88,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
88 $this->assertEquals('20150310_114651', $link['linkdate']); 88 $this->assertEquals('20150310_114651', $link['linkdate']);
89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']); 89 $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
90 $this->assertEquals('http://host.tld/?WDWyig', $link['url']); 90 $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
91 $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']); 91 $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['iso_date']);
92 $this->assertContains('Stallman has a beard', $link['description']); 92 $this->assertContains('Stallman has a beard', $link['description']);
93 $this->assertContains('Permalink', $link['description']); 93 $this->assertContains('Permalink', $link['description']);
94 $this->assertContains('http://host.tld/?WDWyig', $link['description']); 94 $this->assertContains('http://host.tld/?WDWyig', $link['description']);
@@ -113,7 +113,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
113 $data = $feedBuilder->buildData(); 113 $data = $feedBuilder->buildData();
114 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); 114 $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
115 $link = array_shift($data['links']); 115 $link = array_shift($data['links']);
116 $this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']); 116 $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:+\d{2}/', $link['iso_date']);
117 } 117 }
118 118
119 /** 119 /**
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 30ea4e01..9c64188f 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -101,7 +101,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
101 * Attempt to instantiate a LinkDB whereas the datastore is not writable 101 * Attempt to instantiate a LinkDB whereas the datastore is not writable
102 * 102 *
103 * @expectedException IOException 103 * @expectedException IOException
104 * @expectedExceptionMessageRegExp /Error accessing null/ 104 * @expectedExceptionMessageRegExp /Error accessing\nnull/
105 */ 105 */
106 public function testConstructDatastoreNotWriteable() 106 public function testConstructDatastoreNotWriteable()
107 { 107 {
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php
index 348082c7..61efce68 100644
--- a/tests/PluginManagerTest.php
+++ b/tests/PluginManagerTest.php
@@ -24,29 +24,38 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
24 private static $pluginName = 'test'; 24 private static $pluginName = 'test';
25 25
26 /** 26 /**
27 * @var PluginManager $pluginManager Plugin Mananger instance.
28 */
29 protected $pluginManager;
30
31 public function setUp()
32 {
33 $conf = new ConfigManager('');
34 $this->pluginManager = new PluginManager($conf);
35 }
36
37 /**
27 * Test plugin loading and hook execution. 38 * Test plugin loading and hook execution.
28 * 39 *
29 * @return void 40 * @return void
30 */ 41 */
31 public function testPlugin() 42 public function testPlugin()
32 { 43 {
33 $pluginManager = PluginManager::getInstance();
34
35 PluginManager::$PLUGINS_PATH = self::$pluginPath; 44 PluginManager::$PLUGINS_PATH = self::$pluginPath;
36 $pluginManager->load(array(self::$pluginName)); 45 $this->pluginManager->load(array(self::$pluginName));
37 46
38 $this->assertTrue(function_exists('hook_test_random')); 47 $this->assertTrue(function_exists('hook_test_random'));
39 48
40 $data = array(0 => 'woot'); 49 $data = array(0 => 'woot');
41 $pluginManager->executeHooks('random', $data); 50 $this->pluginManager->executeHooks('random', $data);
42 $this->assertEquals('woot', $data[1]); 51 $this->assertEquals('woot', $data[1]);
43 52
44 $data = array(0 => 'woot'); 53 $data = array(0 => 'woot');
45 $pluginManager->executeHooks('random', $data, array('target' => 'test')); 54 $this->pluginManager->executeHooks('random', $data, array('target' => 'test'));
46 $this->assertEquals('page test', $data[1]); 55 $this->assertEquals('page test', $data[1]);
47 56
48 $data = array(0 => 'woot'); 57 $data = array(0 => 'woot');
49 $pluginManager->executeHooks('random', $data, array('loggedin' => true)); 58 $this->pluginManager->executeHooks('random', $data, array('loggedin' => true));
50 $this->assertEquals('loggedin', $data[1]); 59 $this->assertEquals('loggedin', $data[1]);
51 } 60 }
52 61
@@ -57,11 +66,8 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
57 */ 66 */
58 public function testPluginNotFound() 67 public function testPluginNotFound()
59 { 68 {
60 $pluginManager = PluginManager::getInstance(); 69 $this->pluginManager->load(array());
61 70 $this->pluginManager->load(array('nope', 'renope'));
62 $pluginManager->load(array());
63
64 $pluginManager->load(array('nope', 'renope'));
65 } 71 }
66 72
67 /** 73 /**
@@ -69,16 +75,14 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
69 */ 75 */
70 public function testGetPluginsMeta() 76 public function testGetPluginsMeta()
71 { 77 {
72 $pluginManager = PluginManager::getInstance();
73
74 PluginManager::$PLUGINS_PATH = self::$pluginPath; 78 PluginManager::$PLUGINS_PATH = self::$pluginPath;
75 $pluginManager->load(array(self::$pluginName)); 79 $this->pluginManager->load(array(self::$pluginName));
76 80
77 $expectedParameters = array( 81 $expectedParameters = array(
78 'pop' => '', 82 'pop' => '',
79 'hip' => '', 83 'hip' => '',
80 ); 84 );
81 $meta = $pluginManager->getPluginsMeta(); 85 $meta = $this->pluginManager->getPluginsMeta();
82 $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); 86 $this->assertEquals('test plugin', $meta[self::$pluginName]['description']);
83 $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); 87 $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']);
84 } 88 }
diff --git a/tests/Updater/DummyUpdater.php b/tests/Updater/DummyUpdater.php
index e9ef2aaa..a0be4413 100644
--- a/tests/Updater/DummyUpdater.php
+++ b/tests/Updater/DummyUpdater.php
@@ -11,14 +11,14 @@ class DummyUpdater extends Updater
11 /** 11 /**
12 * Object constructor. 12 * Object constructor.
13 * 13 *
14 * @param array $doneUpdates Updates which are already done. 14 * @param array $doneUpdates Updates which are already done.
15 * @param array $config Shaarli's configuration array. 15 * @param LinkDB $linkDB LinkDB instance.
16 * @param LinkDB $linkDB LinkDB instance. 16 * @param ConfigManager $conf Configuration Manager instance.
17 * @param boolean $isLoggedIn True if the user is logged in. 17 * @param boolean $isLoggedIn True if the user is logged in.
18 */ 18 */
19 public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn) 19 public function __construct($doneUpdates, $linkDB, $conf, $isLoggedIn)
20 { 20 {
21 parent::__construct($doneUpdates, $config, $linkDB, $isLoggedIn); 21 parent::__construct($doneUpdates, $linkDB, $conf, $isLoggedIn);
22 22
23 // Retrieve all update methods. 23 // Retrieve all update methods.
24 // For unit test, only retrieve final methods, 24 // For unit test, only retrieve final methods,
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index a29d9067..6bdce08b 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -1,5 +1,6 @@
1<?php 1<?php
2 2
3require_once 'application/config/ConfigManager.php';
3require_once 'tests/Updater/DummyUpdater.php'; 4require_once 'tests/Updater/DummyUpdater.php';
4 5
5/** 6/**
@@ -9,58 +10,26 @@ require_once 'tests/Updater/DummyUpdater.php';
9class UpdaterTest extends PHPUnit_Framework_TestCase 10class UpdaterTest extends PHPUnit_Framework_TestCase
10{ 11{
11 /** 12 /**
12 * @var array Configuration input set. 13 * @var string Path to test datastore.
13 */ 14 */
14 private static $configFields; 15 protected static $testDatastore = 'sandbox/datastore.php';
15 16
16 /** 17 /**
17 * @var string Path to test datastore. 18 * @var string Config file path (without extension).
18 */ 19 */
19 protected static $testDatastore = 'sandbox/datastore.php'; 20 protected static $configFile = 'tests/utils/config/configJson';
20 21
21 /** 22 /**
22 * Executed before each test. 23 * @var ConfigManager
23 */ 24 */
24 public function setUp() 25 protected $conf;
25 {
26 self::$configFields = array(
27 'login' => 'login',
28 'hash' => 'hash',
29 'salt' => 'salt',
30 'timezone' => 'Europe/Paris',
31 'title' => 'title',
32 'titleLink' => 'titleLink',
33 'redirector' => '',
34 'disablesessionprotection' => false,
35 'privateLinkByDefault' => false,
36 'config' => array(
37 'CONFIG_FILE' => 'tests/Updater/config.php',
38 'DATADIR' => 'tests/Updater',
39 'PAGECACHE' => 'sandbox/pagecache',
40 'config1' => 'config1data',
41 'config2' => 'config2data',
42 )
43 );
44 }
45 26
46 /** 27 /**
47 * Executed after each test. 28 * Executed before each test.
48 *
49 * @return void
50 */ 29 */
51 public function tearDown() 30 public function setUp()
52 { 31 {
53 if (is_file(self::$configFields['config']['CONFIG_FILE'])) { 32 $this->conf = new ConfigManager(self::$configFile);
54 unlink(self::$configFields['config']['CONFIG_FILE']);
55 }
56
57 if (is_file(self::$configFields['config']['DATADIR'] . '/options.php')) {
58 unlink(self::$configFields['config']['DATADIR'] . '/options.php');
59 }
60
61 if (is_file(self::$configFields['config']['DATADIR'] . '/updates.json')) {
62 unlink(self::$configFields['config']['DATADIR'] . '/updates.json');
63 }
64 } 33 }
65 34
66 /** 35 /**
@@ -69,9 +38,10 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
69 public function testReadEmptyUpdatesFile() 38 public function testReadEmptyUpdatesFile()
70 { 39 {
71 $this->assertEquals(array(), read_updates_file('')); 40 $this->assertEquals(array(), read_updates_file(''));
72 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 41 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
73 touch($updatesFile); 42 touch($updatesFile);
74 $this->assertEquals(array(), read_updates_file($updatesFile)); 43 $this->assertEquals(array(), read_updates_file($updatesFile));
44 unlink($updatesFile);
75 } 45 }
76 46
77 /** 47 /**
@@ -79,7 +49,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
79 */ 49 */
80 public function testReadWriteUpdatesFile() 50 public function testReadWriteUpdatesFile()
81 { 51 {
82 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 52 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
83 $updatesMethods = array('m1', 'm2', 'm3'); 53 $updatesMethods = array('m1', 'm2', 'm3');
84 54
85 write_updates_file($updatesFile, $updatesMethods); 55 write_updates_file($updatesFile, $updatesMethods);
@@ -91,6 +61,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
91 write_updates_file($updatesFile, $updatesMethods); 61 write_updates_file($updatesFile, $updatesMethods);
92 $readMethods = read_updates_file($updatesFile); 62 $readMethods = read_updates_file($updatesFile);
93 $this->assertEquals($readMethods, $updatesMethods); 63 $this->assertEquals($readMethods, $updatesMethods);
64 unlink($updatesFile);
94 } 65 }
95 66
96 /** 67 /**
@@ -112,10 +83,15 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
112 */ 83 */
113 public function testWriteUpdatesFileNotWritable() 84 public function testWriteUpdatesFileNotWritable()
114 { 85 {
115 $updatesFile = self::$configFields['config']['DATADIR'] . '/updates.json'; 86 $updatesFile = $this->conf->get('resource.data_dir') . '/updates.txt';
116 touch($updatesFile); 87 touch($updatesFile);
117 chmod($updatesFile, 0444); 88 chmod($updatesFile, 0444);
118 @write_updates_file($updatesFile, array('test')); 89 try {
90 @write_updates_file($updatesFile, array('test'));
91 } catch (Exception $e) {
92 unlink($updatesFile);
93 throw $e;
94 }
119 } 95 }
120 96
121 /** 97 /**
@@ -131,10 +107,10 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
131 'updateMethodDummy3', 107 'updateMethodDummy3',
132 'updateMethodException', 108 'updateMethodException',
133 ); 109 );
134 $updater = new DummyUpdater($updates, array(), array(), true); 110 $updater = new DummyUpdater($updates, array(), $this->conf, true);
135 $this->assertEquals(array(), $updater->update()); 111 $this->assertEquals(array(), $updater->update());
136 112
137 $updater = new DummyUpdater(array(), array(), array(), false); 113 $updater = new DummyUpdater(array(), array(), $this->conf, false);
138 $this->assertEquals(array(), $updater->update()); 114 $this->assertEquals(array(), $updater->update());
139 } 115 }
140 116
@@ -149,7 +125,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
149 'updateMethodDummy2', 125 'updateMethodDummy2',
150 'updateMethodDummy3', 126 'updateMethodDummy3',
151 ); 127 );
152 $updater = new DummyUpdater($updates, array(), array(), true); 128 $updater = new DummyUpdater($updates, array(), $this->conf, true);
153 $this->assertEquals($expectedUpdates, $updater->update()); 129 $this->assertEquals($expectedUpdates, $updater->update());
154 } 130 }
155 131
@@ -165,7 +141,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
165 ); 141 );
166 $expectedUpdate = array('updateMethodDummy2'); 142 $expectedUpdate = array('updateMethodDummy2');
167 143
168 $updater = new DummyUpdater($updates, array(), array(), true); 144 $updater = new DummyUpdater($updates, array(), $this->conf, true);
169 $this->assertEquals($expectedUpdate, $updater->update()); 145 $this->assertEquals($expectedUpdate, $updater->update());
170 } 146 }
171 147
@@ -182,7 +158,7 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
182 'updateMethodDummy3', 158 'updateMethodDummy3',
183 ); 159 );
184 160
185 $updater = new DummyUpdater($updates, array(), array(), true); 161 $updater = new DummyUpdater($updates, array(), $this->conf, true);
186 $updater->update(); 162 $updater->update();
187 } 163 }
188 164
@@ -195,26 +171,28 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
195 */ 171 */
196 public function testUpdateMergeDeprecatedConfig() 172 public function testUpdateMergeDeprecatedConfig()
197 { 173 {
198 // init 174 $this->conf->setConfigFile('tests/utils/config/configPhp');
199 writeConfig(self::$configFields, true); 175 $this->conf->reset();
200 $configCopy = self::$configFields;
201 $invert = !$configCopy['privateLinkByDefault'];
202 $configCopy['privateLinkByDefault'] = $invert;
203 176
204 // Use writeConfig to create a options.php 177 $optionsFile = 'tests/Updater/options.php';
205 $configCopy['config']['CONFIG_FILE'] = 'tests/Updater/options.php'; 178 $options = '<?php
206 writeConfig($configCopy, true); 179$GLOBALS[\'privateLinkByDefault\'] = true;';
180 file_put_contents($optionsFile, $options);
207 181
208 $this->assertTrue(is_file($configCopy['config']['CONFIG_FILE'])); 182 // tmp config file.
183 $this->conf->setConfigFile('tests/Updater/config');
209 184
210 // merge configs 185 // merge configs
211 $updater = new Updater(array(), self::$configFields, array(), true); 186 $updater = new Updater(array(), array(), $this->conf, true);
187 // This writes a new config file in tests/Updater/config.php
212 $updater->updateMethodMergeDeprecatedConfigFile(); 188 $updater->updateMethodMergeDeprecatedConfigFile();
213 189
214 // make sure updated field is changed 190 // make sure updated field is changed
215 include self::$configFields['config']['CONFIG_FILE']; 191 $this->conf->reload();
216 $this->assertEquals($invert, $GLOBALS['privateLinkByDefault']); 192 $this->assertTrue($this->conf->get('privacy.default_private_links'));
217 $this->assertFalse(is_file($configCopy['config']['CONFIG_FILE'])); 193 $this->assertFalse(is_file($optionsFile));
194 // Delete the generated file.
195 unlink($this->conf->getConfigFileExt());
218 } 196 }
219 197
220 /** 198 /**
@@ -222,23 +200,67 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
222 */ 200 */
223 public function testMergeDeprecatedConfigNoFile() 201 public function testMergeDeprecatedConfigNoFile()
224 { 202 {
225 writeConfig(self::$configFields, true); 203 $updater = new Updater(array(), array(), $this->conf, true);
226
227 $updater = new Updater(array(), self::$configFields, array(), true);
228 $updater->updateMethodMergeDeprecatedConfigFile(); 204 $updater->updateMethodMergeDeprecatedConfigFile();
229 205
230 include self::$configFields['config']['CONFIG_FILE']; 206 $this->assertEquals('root', $this->conf->get('credentials.login'));
231 $this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
232 } 207 }
233 208
209 /**
210 * Test renameDashTags update method.
211 */
234 public function testRenameDashTags() 212 public function testRenameDashTags()
235 { 213 {
236 $refDB = new ReferenceLinkDB(); 214 $refDB = new ReferenceLinkDB();
237 $refDB->write(self::$testDatastore); 215 $refDB->write(self::$testDatastore);
238 $linkDB = new LinkDB(self::$testDatastore, true, false); 216 $linkDB = new LinkDB(self::$testDatastore, true, false);
239 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 217 $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
240 $updater = new Updater(array(), self::$configFields, $linkDB, true); 218 $updater = new Updater(array(), $linkDB, $this->conf, true);
241 $updater->updateMethodRenameDashTags(); 219 $updater->updateMethodRenameDashTags();
242 $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); 220 $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
243 } 221 }
222
223 /**
224 * Convert old PHP config file to JSON config.
225 */
226 public function testConfigToJson()
227 {
228 $configFile = 'tests/utils/config/configPhp';
229 $this->conf->setConfigFile($configFile);
230 $this->conf->reset();
231
232 // The ConfigIO is initialized with ConfigPhp.
233 $this->assertTrue($this->conf->getConfigIO() instanceof ConfigPhp);
234
235 $updater = new Updater(array(), array(), $this->conf, false);
236 $done = $updater->updateMethodConfigToJson();
237 $this->assertTrue($done);
238
239 // The ConfigIO has been updated to ConfigJson.
240 $this->assertTrue($this->conf->getConfigIO() instanceof ConfigJson);
241 $this->assertTrue(file_exists($this->conf->getConfigFileExt()));
242
243 // Check JSON config data.
244 $this->conf->reload();
245 $this->assertEquals('root', $this->conf->get('credentials.login'));
246 $this->assertEquals('lala', $this->conf->get('redirector.url'));
247 $this->assertEquals('data/datastore.php', $this->conf->get('resource.datastore'));
248 $this->assertEquals('1', $this->conf->get('plugins.WALLABAG_VERSION'));
249
250 rename($configFile . '.save.php', $configFile . '.php');
251 unlink($this->conf->getConfigFileExt());
252 }
253
254 /**
255 * Launch config conversion update with an existing JSON file => nothing to do.
256 */
257 public function testConfigToJsonNothingToDo()
258 {
259 $filetime = filemtime($this->conf->getConfigFileExt());
260 $updater = new Updater(array(), array(), $this->conf, false);
261 $done = $updater->updateMethodConfigToJson();
262 $this->assertTrue($done);
263 $expected = filemtime($this->conf->getConfigFileExt());
264 $this->assertEquals($expected, $filetime);
265 }
244} 266}
diff --git a/tests/config/ConfigJsonTest.php b/tests/config/ConfigJsonTest.php
new file mode 100644
index 00000000..99c88820
--- /dev/null
+++ b/tests/config/ConfigJsonTest.php
@@ -0,0 +1,133 @@
1<?php
2
3require_once 'application/config/ConfigJson.php';
4
5/**
6 * Class ConfigJsonTest
7 */
8class ConfigJsonTest extends PHPUnit_Framework_TestCase
9{
10 /**
11 * @var ConfigJson
12 */
13 protected $configIO;
14
15 public function setUp()
16 {
17 $this->configIO = new ConfigJson();
18 }
19
20 /**
21 * Read a simple existing config file.
22 */
23 public function testRead()
24 {
25 $conf = $this->configIO->read('tests/utils/config/configJson.json.php');
26 $this->assertEquals('root', $conf['credentials']['login']);
27 $this->assertEquals('lala', $conf['redirector']['url']);
28 $this->assertEquals('tests/utils/config/datastore.php', $conf['resource']['datastore']);
29 $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
30 }
31
32 /**
33 * Read a non existent config file -> empty array.
34 */
35 public function testReadNonExistent()
36 {
37 $this->assertEquals(array(), $this->configIO->read('nope'));
38 }
39
40 /**
41 * Read a non existent config file -> empty array.
42 *
43 * @expectedException Exception
44 * @expectedExceptionMessage An error occured while parsing JSON file: error code #4
45 */
46 public function testReadInvalidJson()
47 {
48 $this->configIO->read('tests/utils/config/configInvalid.json.php');
49 }
50
51 /**
52 * Write a new config file.
53 */
54 public function testWriteNew()
55 {
56 $dataFile = 'tests/utils/config/configWrite.json.php';
57 $data = array(
58 'credentials' => array(
59 'login' => 'root',
60 ),
61 'resource' => array(
62 'datastore' => 'data/datastore.php',
63 ),
64 'redirector' => array(
65 'url' => 'lala',
66 ),
67 'plugins' => array(
68 'WALLABAG_VERSION' => '1',
69 )
70 );
71 $this->configIO->write($dataFile, $data);
72 // PHP 5.3 doesn't support json pretty print.
73 if (defined('JSON_PRETTY_PRINT')) {
74 $expected = '{
75 "credentials": {
76 "login": "root"
77 },
78 "resource": {
79 "datastore": "data\/datastore.php"
80 },
81 "redirector": {
82 "url": "lala"
83 },
84 "plugins": {
85 "WALLABAG_VERSION": "1"
86 }
87}';
88 } else {
89 $expected = '{"credentials":{"login":"root"},"resource":{"datastore":"data\/datastore.php"},"redirector":{"url":"lala"},"plugins":{"WALLABAG_VERSION":"1"}}';
90 }
91 $expected = ConfigJson::getPhpHeaders() . $expected . ConfigJson::getPhpSuffix();
92 $this->assertEquals($expected, file_get_contents($dataFile));
93 unlink($dataFile);
94 }
95
96 /**
97 * Overwrite an existing setting.
98 */
99 public function testOverwrite()
100 {
101 $source = 'tests/utils/config/configJson.json.php';
102 $dest = 'tests/utils/config/configOverwrite.json.php';
103 copy($source, $dest);
104 $conf = $this->configIO->read($dest);
105 $conf['redirector']['url'] = 'blabla';
106 $this->configIO->write($dest, $conf);
107 $conf = $this->configIO->read($dest);
108 $this->assertEquals('blabla', $conf['redirector']['url']);
109 unlink($dest);
110 }
111
112 /**
113 * Write to invalid path.
114 *
115 * @expectedException IOException
116 */
117 public function testWriteInvalidArray()
118 {
119 $conf = array('conf' => 'value');
120 @$this->configIO->write(array(), $conf);
121 }
122
123 /**
124 * Write to invalid path.
125 *
126 * @expectedException IOException
127 */
128 public function testWriteInvalidBlank()
129 {
130 $conf = array('conf' => 'value');
131 @$this->configIO->write('', $conf);
132 }
133}
diff --git a/tests/config/ConfigManagerTest.php b/tests/config/ConfigManagerTest.php
new file mode 100644
index 00000000..436e3d67
--- /dev/null
+++ b/tests/config/ConfigManagerTest.php
@@ -0,0 +1,172 @@
1<?php
2
3/**
4 * Unit tests for Class ConfigManagerTest
5 *
6 * Note: it only test the manager with ConfigJson,
7 * ConfigPhp is only a workaround to handle the transition to JSON type.
8 */
9class ConfigManagerTest extends PHPUnit_Framework_TestCase
10{
11 /**
12 * @var ConfigManager
13 */
14 protected $conf;
15
16 public function setUp()
17 {
18 $this->conf = new ConfigManager('tests/utils/config/configJson');
19 }
20
21 /**
22 * Simple config test:
23 * 1. Set settings.
24 * 2. Check settings value.
25 */
26 public function testSetGet()
27 {
28 $this->conf->set('paramInt', 42);
29 $this->conf->set('paramString', 'value1');
30 $this->conf->set('paramBool', false);
31 $this->conf->set('paramArray', array('foo' => 'bar'));
32 $this->conf->set('paramNull', null);
33
34 $this->assertEquals(42, $this->conf->get('paramInt'));
35 $this->assertEquals('value1', $this->conf->get('paramString'));
36 $this->assertFalse($this->conf->get('paramBool'));
37 $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
38 $this->assertEquals(null, $this->conf->get('paramNull'));
39 }
40
41 /**
42 * Set/write/get config test:
43 * 1. Set settings.
44 * 2. Write it to the config file.
45 * 3. Read the file.
46 * 4. Check settings value.
47 */
48 public function testSetWriteGet()
49 {
50 $this->conf->set('paramInt', 42);
51 $this->conf->set('paramString', 'value1');
52 $this->conf->set('paramBool', false);
53 $this->conf->set('paramArray', array('foo' => 'bar'));
54 $this->conf->set('paramNull', null);
55
56 $this->conf->setConfigFile('tests/utils/config/configTmp');
57 $this->conf->write(true);
58 $this->conf->reload();
59 unlink($this->conf->getConfigFileExt());
60
61 $this->assertEquals(42, $this->conf->get('paramInt'));
62 $this->assertEquals('value1', $this->conf->get('paramString'));
63 $this->assertFalse($this->conf->get('paramBool'));
64 $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
65 $this->assertEquals(null, $this->conf->get('paramNull'));
66 }
67
68 /**
69 * Test set/write/get with nested keys.
70 */
71 public function testSetWriteGetNested()
72 {
73 $this->conf->set('foo.bar.key.stuff', 'testSetWriteGetNested');
74
75 $this->conf->setConfigFile('tests/utils/config/configTmp');
76 $this->conf->write(true);
77 $this->conf->reload();
78 unlink($this->conf->getConfigFileExt());
79
80 $this->assertEquals('testSetWriteGetNested', $this->conf->get('foo.bar.key.stuff'));
81 }
82
83 /**
84 * Set with an empty key.
85 *
86 * @expectedException Exception
87 * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
88 */
89 public function testSetEmptyKey()
90 {
91 $this->conf->set('', 'stuff');
92 }
93
94 /**
95 * Set with an array key.
96 *
97 * @expectedException Exception
98 * @expectedExceptionMessageRegExp #^Invalid setting key parameter. String expected, got.*#
99 */
100 public function testSetArrayKey()
101 {
102 $this->conf->set(array('foo' => 'bar'), 'stuff');
103 }
104
105 /**
106 * Try to write the config without mandatory parameter (e.g. 'login').
107 *
108 * @expectedException MissingFieldConfigException
109 */
110 public function testWriteMissingParameter()
111 {
112 $this->conf->setConfigFile('tests/utils/config/configTmp');
113 $this->assertFalse(file_exists($this->conf->getConfigFileExt()));
114 $this->conf->reload();
115
116 $this->conf->write(true);
117 }
118
119 /**
120 * Try to get non existent config keys.
121 */
122 public function testGetNonExistent()
123 {
124 $this->assertEquals('', $this->conf->get('nope.test'));
125 $this->assertEquals('default', $this->conf->get('nope.test', 'default'));
126 }
127
128 /**
129 * Test the 'exists' method with existent values.
130 */
131 public function testExistsOk()
132 {
133 $this->assertTrue($this->conf->exists('credentials.login'));
134 $this->assertTrue($this->conf->exists('config.foo'));
135 }
136
137 /**
138 * Test the 'exists' method with non existent or invalid values.
139 */
140 public function testExistsKo()
141 {
142 $this->assertFalse($this->conf->exists('nope'));
143 $this->assertFalse($this->conf->exists('nope.nope'));
144 $this->assertFalse($this->conf->exists(''));
145 $this->assertFalse($this->conf->exists(false));
146 }
147
148 /**
149 * Reset the ConfigManager instance.
150 */
151 public function testReset()
152 {
153 $confIO = $this->conf->getConfigIO();
154 $this->conf->reset();
155 $this->assertFalse($confIO === $this->conf->getConfigIO());
156 }
157
158 /**
159 * Reload the config from file.
160 */
161 public function testReload()
162 {
163 $this->conf->setConfigFile('tests/utils/config/configTmp');
164 $newConf = ConfigJson::getPhpHeaders() . '{ "key": "value" }';
165 file_put_contents($this->conf->getConfigFileExt(), $newConf);
166 $this->conf->reload();
167 unlink($this->conf->getConfigFileExt());
168 // Previous conf no longer exists, and new values have been loaded.
169 $this->assertFalse($this->conf->exists('credentials.login'));
170 $this->assertEquals('value', $this->conf->get('key'));
171 }
172}
diff --git a/tests/config/ConfigPhpTest.php b/tests/config/ConfigPhpTest.php
new file mode 100644
index 00000000..58cd8d2a
--- /dev/null
+++ b/tests/config/ConfigPhpTest.php
@@ -0,0 +1,82 @@
1<?php
2
3require_once 'application/config/ConfigPhp.php';
4
5/**
6 * Class ConfigPhpTest
7 */
8class ConfigPhpTest extends PHPUnit_Framework_TestCase
9{
10 /**
11 * @var ConfigPhp
12 */
13 protected $configIO;
14
15 public function setUp()
16 {
17 $this->configIO = new ConfigPhp();
18 }
19
20 /**
21 * Read a simple existing config file.
22 */
23 public function testRead()
24 {
25 $conf = $this->configIO->read('tests/utils/config/configPhp.php');
26 $this->assertEquals('root', $conf['login']);
27 $this->assertEquals('lala', $conf['redirector']);
28 $this->assertEquals('data/datastore.php', $conf['config']['DATASTORE']);
29 $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
30 }
31
32 /**
33 * Read a non existent config file -> empty array.
34 */
35 public function testReadNonExistent()
36 {
37 $this->assertEquals(array(), $this->configIO->read('nope'));
38 }
39
40 /**
41 * Write a new config file.
42 */
43 public function testWriteNew()
44 {
45 $dataFile = 'tests/utils/config/configWrite.php';
46 $data = array(
47 'login' => 'root',
48 'redirector' => 'lala',
49 'config' => array(
50 'DATASTORE' => 'data/datastore.php',
51 ),
52 'plugins' => array(
53 'WALLABAG_VERSION' => '1',
54 )
55 );
56 $this->configIO->write($dataFile, $data);
57 $expected = '<?php
58$GLOBALS[\'login\'] = \'root\';
59$GLOBALS[\'redirector\'] = \'lala\';
60$GLOBALS[\'config\'][\'DATASTORE\'] = \'data/datastore.php\';
61$GLOBALS[\'plugins\'][\'WALLABAG_VERSION\'] = \'1\';
62';
63 $this->assertEquals($expected, file_get_contents($dataFile));
64 unlink($dataFile);
65 }
66
67 /**
68 * Overwrite an existing setting.
69 */
70 public function testOverwrite()
71 {
72 $source = 'tests/utils/config/configPhp.php';
73 $dest = 'tests/utils/config/configOverwrite.php';
74 copy($source, $dest);
75 $conf = $this->configIO->read($dest);
76 $conf['redirector'] = 'blabla';
77 $this->configIO->write($dest, $conf);
78 $conf = $this->configIO->read($dest);
79 $this->assertEquals('blabla', $conf['redirector']);
80 unlink($dest);
81 }
82}
diff --git a/tests/config/ConfigPluginTest.php b/tests/config/ConfigPluginTest.php
new file mode 100644
index 00000000..716631b0
--- /dev/null
+++ b/tests/config/ConfigPluginTest.php
@@ -0,0 +1,121 @@
1<?php
2/**
3 * Config' tests
4 */
5
6require_once 'application/config/ConfigPlugin.php';
7
8/**
9 * Unitary tests for Shaarli config related functions
10 */
11class ConfigPluginTest extends PHPUnit_Framework_TestCase
12{
13 /**
14 * Test save_plugin_config with valid data.
15 *
16 * @throws PluginConfigOrderException
17 */
18 public function testSavePluginConfigValid()
19 {
20 $data = array(
21 'order_plugin1' => 2, // no plugin related
22 'plugin2' => 0, // new - at the end
23 'plugin3' => 0, // 2nd
24 'order_plugin3' => 8,
25 'plugin4' => 0, // 1st
26 'order_plugin4' => 5,
27 );
28
29 $expected = array(
30 'plugin3',
31 'plugin4',
32 'plugin2',
33 );
34
35 $out = save_plugin_config($data);
36 $this->assertEquals($expected, $out);
37 }
38
39 /**
40 * Test save_plugin_config with invalid data.
41 *
42 * @expectedException PluginConfigOrderException
43 */
44 public function testSavePluginConfigInvalid()
45 {
46 $data = array(
47 'plugin2' => 0,
48 'plugin3' => 0,
49 'order_plugin3' => 0,
50 'plugin4' => 0,
51 'order_plugin4' => 0,
52 );
53
54 save_plugin_config($data);
55 }
56
57 /**
58 * Test save_plugin_config without data.
59 */
60 public function testSavePluginConfigEmpty()
61 {
62 $this->assertEquals(array(), save_plugin_config(array()));
63 }
64
65 /**
66 * Test validate_plugin_order with valid data.
67 */
68 public function testValidatePluginOrderValid()
69 {
70 $data = array(
71 'order_plugin1' => 2,
72 'plugin2' => 0,
73 'plugin3' => 0,
74 'order_plugin3' => 1,
75 'plugin4' => 0,
76 'order_plugin4' => 5,
77 );
78
79 $this->assertTrue(validate_plugin_order($data));
80 }
81
82 /**
83 * Test validate_plugin_order with invalid data.
84 */
85 public function testValidatePluginOrderInvalid()
86 {
87 $data = array(
88 'order_plugin1' => 2,
89 'order_plugin3' => 1,
90 'order_plugin4' => 1,
91 );
92
93 $this->assertFalse(validate_plugin_order($data));
94 }
95
96 /**
97 * Test load_plugin_parameter_values.
98 */
99 public function testLoadPluginParameterValues()
100 {
101 $plugins = array(
102 'plugin_name' => array(
103 'parameters' => array(
104 'param1' => true,
105 'param2' => false,
106 'param3' => '',
107 )
108 )
109 );
110
111 $parameters = array(
112 'param1' => 'value1',
113 'param2' => 'value2',
114 );
115
116 $result = load_plugin_parameter_values($plugins, $parameters);
117 $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
118 $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
119 $this->assertEquals('', $result['plugin_name']['parameters']['param3']);
120 }
121}
diff --git a/tests/plugins/PluginReadityourselfTest.php b/tests/plugins/PluginReadityourselfTest.php
index 8bf17bf1..d73e666a 100644
--- a/tests/plugins/PluginReadityourselfTest.php
+++ b/tests/plugins/PluginReadityourselfTest.php
@@ -4,6 +4,8 @@
4 * PluginReadityourselfTest.php.php 4 * PluginReadityourselfTest.php.php
5 */ 5 */
6 6
7// FIXME! add an init method.
8$conf = new ConfigManager('');
7require_once 'plugins/readityourself/readityourself.php'; 9require_once 'plugins/readityourself/readityourself.php';
8 10
9/** 11/**
@@ -25,7 +27,8 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
25 */ 27 */
26 function testReadityourselfLinklist() 28 function testReadityourselfLinklist()
27 { 29 {
28 $GLOBALS['plugins']['READITYOUSELF_URL'] = 'value'; 30 $conf = new ConfigManager('');
31 $conf->set('plugins.READITYOUSELF_URL', 'value');
29 $str = 'http://randomstr.com/test'; 32 $str = 'http://randomstr.com/test';
30 $data = array( 33 $data = array(
31 'title' => $str, 34 'title' => $str,
@@ -36,7 +39,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
36 ) 39 )
37 ); 40 );
38 41
39 $data = hook_readityourself_render_linklist($data); 42 $data = hook_readityourself_render_linklist($data, $conf);
40 $link = $data['links'][0]; 43 $link = $data['links'][0];
41 // data shouldn't be altered 44 // data shouldn't be altered
42 $this->assertEquals($str, $data['title']); 45 $this->assertEquals($str, $data['title']);
@@ -52,7 +55,8 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
52 */ 55 */
53 function testReadityourselfLinklistWithoutConfig() 56 function testReadityourselfLinklistWithoutConfig()
54 { 57 {
55 unset($GLOBALS['plugins']['READITYOUSELF_URL']); 58 $conf = new ConfigManager('');
59 $conf->set('plugins.READITYOUSELF_URL', null);
56 $str = 'http://randomstr.com/test'; 60 $str = 'http://randomstr.com/test';
57 $data = array( 61 $data = array(
58 'title' => $str, 62 'title' => $str,
@@ -63,7 +67,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
63 ) 67 )
64 ); 68 );
65 69
66 $data = hook_readityourself_render_linklist($data); 70 $data = hook_readityourself_render_linklist($data, $conf);
67 $link = $data['links'][0]; 71 $link = $data['links'][0];
68 // data shouldn't be altered 72 // data shouldn't be altered
69 $this->assertEquals($str, $data['title']); 73 $this->assertEquals($str, $data['title']);
diff --git a/tests/plugins/PluginWallabagTest.php b/tests/plugins/PluginWallabagTest.php
index 5d3a60e0..302ee296 100644
--- a/tests/plugins/PluginWallabagTest.php
+++ b/tests/plugins/PluginWallabagTest.php
@@ -4,6 +4,8 @@
4 * PluginWallabagTest.php.php 4 * PluginWallabagTest.php.php
5 */ 5 */
6 6
7// FIXME! add an init method.
8$conf = new ConfigManager('');
7require_once 'plugins/wallabag/wallabag.php'; 9require_once 'plugins/wallabag/wallabag.php';
8 10
9/** 11/**
@@ -25,7 +27,8 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
25 */ 27 */
26 function testWallabagLinklist() 28 function testWallabagLinklist()
27 { 29 {
28 $GLOBALS['plugins']['WALLABAG_URL'] = 'value'; 30 $conf = new ConfigManager('');
31 $conf->set('plugins.WALLABAG_URL', 'value');
29 $str = 'http://randomstr.com/test'; 32 $str = 'http://randomstr.com/test';
30 $data = array( 33 $data = array(
31 'title' => $str, 34 'title' => $str,
@@ -36,7 +39,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
36 ) 39 )
37 ); 40 );
38 41
39 $data = hook_wallabag_render_linklist($data); 42 $data = hook_wallabag_render_linklist($data, $conf);
40 $link = $data['links'][0]; 43 $link = $data['links'][0];
41 // data shouldn't be altered 44 // data shouldn't be altered
42 $this->assertEquals($str, $data['title']); 45 $this->assertEquals($str, $data['title']);
@@ -45,7 +48,6 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
45 // plugin data 48 // plugin data
46 $this->assertEquals(1, count($link['link_plugin'])); 49 $this->assertEquals(1, count($link['link_plugin']));
47 $this->assertNotFalse(strpos($link['link_plugin'][0], urlencode($str))); 50 $this->assertNotFalse(strpos($link['link_plugin'][0], urlencode($str)));
48 $this->assertNotFalse(strpos($link['link_plugin'][0], $GLOBALS['plugins']['WALLABAG_URL'])); 51 $this->assertNotFalse(strpos($link['link_plugin'][0], $conf->get('plugins.WALLABAG_URL')));
49 } 52 }
50} 53}
51
diff --git a/tests/utils/config/configInvalid.json.php b/tests/utils/config/configInvalid.json.php
new file mode 100644
index 00000000..167f2168
--- /dev/null
+++ b/tests/utils/config/configInvalid.json.php
@@ -0,0 +1,5 @@
1<?php /*
2{
3 bad: bad,
4}
5*/ ?> \ No newline at end of file
diff --git a/tests/utils/config/configJson.json.php b/tests/utils/config/configJson.json.php
new file mode 100644
index 00000000..06a302e8
--- /dev/null
+++ b/tests/utils/config/configJson.json.php
@@ -0,0 +1,34 @@
1<?php /*
2{
3 "credentials": {
4 "login":"root",
5 "hash":"hash",
6 "salt":"salt"
7 },
8 "security": {
9 "session_protection_disabled":false
10 },
11 "general": {
12 "timezone":"Europe\/Paris",
13 "title": "Shaarli",
14 "header_link": "?"
15 },
16 "privacy": {
17 "default_private_links":true
18 },
19 "redirector": {
20 "url":"lala"
21 },
22 "config": {
23 "foo": "bar"
24 },
25 "resource": {
26 "datastore": "tests\/utils\/config\/datastore.php",
27 "data_dir": "tests\/utils\/config"
28 },
29 "plugins": {
30 "WALLABAG_VERSION": 1
31 }
32}
33*/ ?>
34
diff --git a/tests/utils/config/configPhp.php b/tests/utils/config/configPhp.php
new file mode 100644
index 00000000..0e034175
--- /dev/null
+++ b/tests/utils/config/configPhp.php
@@ -0,0 +1,14 @@
1<?php
2$GLOBALS['login'] = 'root';
3$GLOBALS['hash'] = 'hash';
4$GLOBALS['salt'] = 'salt';
5$GLOBALS['timezone'] = 'Europe/Paris';
6$GLOBALS['title'] = 'title';
7$GLOBALS['titleLink'] = 'titleLink';
8$GLOBALS['redirector'] = 'lala';
9$GLOBALS['disablesessionprotection'] = false;
10$GLOBALS['privateLinkByDefault'] = false;
11$GLOBALS['config']['DATADIR'] = 'tests/Updater';
12$GLOBALS['config']['PAGECACHE'] = 'sandbox/pagecache';
13$GLOBALS['config']['DATASTORE'] = 'data/datastore.php';
14$GLOBALS['plugins']['WALLABAG_VERSION'] = '1';
diff --git a/tpl/configure.html b/tpl/configure.html
index 77c8b7d9..ad9a2085 100644
--- a/tpl/configure.html
+++ b/tpl/configure.html
@@ -3,48 +3,90 @@
3<head>{include="includes"}</head> 3<head>{include="includes"}</head>
4<body onload="document.configform.title.focus();"> 4<body onload="document.configform.title.focus();">
5<div id="pageheader"> 5<div id="pageheader">
6 {include="page.header"} 6 {include="page.header"}
7{$timezone_js} 7 {$timezone_js}
8 <form method="POST" action="#" name="configform" id="configform"> 8 <form method="POST" action="#" name="configform" id="configform">
9 <input type="hidden" name="token" value="{$token}"> 9 <input type="hidden" name="token" value="{$token}">
10 <table id="configuration_table"> 10 <table id="configuration_table">
11 11
12 <tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr> 12 <tr>
13 <td><b>Page title:</b></td>
14 <td><input type="text" name="title" id="title" size="50" value="{$title}"></td>
15 </tr>
13 16
14 <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> 17 <tr>
15 <tr><td><b>Timezone:</b></td><td>{$timezone_form}</td></tr> 18 <td><b>Title link:</b></td>
19 <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label
20 for="titleLink">(default value is: ?)</label></td>
21 </tr>
22 <tr>
23 <td><b>Timezone:</b></td>
24 <td>{$timezone_form}</td>
25 </tr>
16 26
17 <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> 27 <tr>
28 <td><b>Redirector</b></td>
29 <td>
30 <input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>
31 (e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)
32 </td>
33 </tr>
18 34
19 <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> 35 <tr>
36 <td><b>Security:</b></td>
37 <td>
38 <input type="checkbox" name="disablesessionprotection" id="disablesessionprotection"
39 {if="$private_links_default"}checked{/if}>
40 <label
41 for="disablesessionprotection">&nbsp;Disable session cookie hijacking protection (Check this if you get
42 disconnected often or if your IP address changes often.)</label>
43 </td>
44 </tr>
20 45
21 <tr><td valign="top"><b>New link:</b></td><td> 46 <tr>
22 <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> 47 <td valign="top"><b>New link:</b></td>
23 </tr> 48 <td>
24 <tr> 49 <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault"
25 <td valign="top"><b>RSS direct links</b></td> 50 {if="$private_links_default"}checked{/if}/>
26 <td> 51 <label for="privateLinkByDefault">
27 <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/> 52 &nbsp;All new links are private by default
28 <label for="enableRssPermalinks"> 53 </label>
29 &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> 54 </td>
30 </label> 55 </tr>
31 </td> 56 <tr>
32 </tr> 57 <td valign="top"><b>RSS direct links</b></td>
33 <tr> 58 <td>
34 <td valign="top"><b>Hide public links</b></td> 59 <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks"
35 <td> 60 {if="$enable_rss_permalinks"}checked{/if}/>
36 <input type="checkbox" name="hidePublicLinks" id="hidePublicLinks" {if="!empty($GLOBALS['config']['HIDE_PUBLIC_LINKS'])"}checked{/if}/><label for="hidePublicLinks">&nbsp; 61 <label for="enableRssPermalinks">
37 Do not show any links if the user is not logged in.</label> 62 &nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>
38 </td> 63 {if="$enable_rss_permalinks"}enabled{else}disabled{/if}.</b>
39 </tr> 64 </label>
40 <tr><td valign="top"><b>Update:</b></td><td> 65 </td>
41 <input type="checkbox" name="updateCheck" id="updateCheck" {if="!empty($GLOBALS['config']['ENABLE_UPDATECHECK'])"}checked{/if}/> 66 </tr>
42 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td> 67 <tr>
43 </tr> 68 <td valign="top"><b>Hide public links</b></td>
69 <td>
70 <input type="checkbox" name="hidePublicLinks" id="hidePublicLinks"
71 {if="$hide_public_links"}checked{/if}/>
72 <label for="hidePublicLinks">&nbsp;Do not show any links if the user is not logged in.</label>
73 </td>
74 </tr>
75 <tr>
76 <td valign="top"><b>Update:</b></td>
77 <td>
78 <input type="checkbox" name="updateCheck" id="updateCheck"
79 {if="$enable_update_check"}checked{/if}/>
80 <label for="updateCheck">&nbsp;Notify me when a new release is ready</label>
81 </td>
82 </tr>
44 83
45 <tr><td></td><td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr> 84 <tr>
46 </table> 85 <td></td>
47 </form> 86 <td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td>
87 </tr>
88 </table>
89 </form>
48</div> 90</div>
49{include="page.footer"} 91{include="page.footer"}
50</body> 92</body>
diff --git a/tpl/daily.html b/tpl/daily.html
index 063dc89a..dde1f376 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -53,7 +53,7 @@
53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink"> 53 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
54 </a> 54 </a>
55 </div> 55 </div>
56 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} 56 {if="!$hide_timestamps || isLoggedIn()"}
57 <div class="dailyEntryLinkdate"> 57 <div class="dailyEntryLinkdate">
58 <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a> 58 <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a>
59 </div> 59 </div>
diff --git a/tpl/dailyrss.html b/tpl/dailyrss.html
index 4133ca3e..b14a3859 100644
--- a/tpl/dailyrss.html
+++ b/tpl/dailyrss.html
@@ -6,7 +6,7 @@
6 <description><![CDATA[ 6 <description><![CDATA[
7 {loop="links"} 7 {loop="links"}
8 <h3><a href="{$value.url}">{$value.title}</a></h3> 8 <h3><a href="{$value.url}">{$value.title}</a></h3>
9 <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> 9 <small>{if="!$hide_timestamps"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
10 {$value.url}</small><br> 10 {$value.url}</small><br>
11 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br> 11 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
12 {if="$value.description"}{$value.formatedDescription}{/if} 12 {if="$value.description"}{$value.formatedDescription}{/if}
diff --git a/tpl/editlink.html b/tpl/editlink.html
index 14a2e6c8..441b5302 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -25,7 +25,7 @@
25 {$value} 25 {$value}
26 {/loop} 26 {/loop}
27 27
28 {if="($link_is_new && $GLOBALS['privateLinkByDefault']==true) || $link.private == true"} 28 {if="($link_is_new && $default_private_links) || $link.private == true"}
29 <input type="checkbox" checked="checked" name="lf_private" id="lf_private"> 29 <input type="checkbox" checked="checked" name="lf_private" id="lf_private">
30 &nbsp;<label for="lf_private"><i>Private</i></label><br> 30 &nbsp;<label for="lf_private"><i>Private</i></label><br>
31 {else} 31 {else}
@@ -43,12 +43,10 @@
43{if="$source !== 'firefoxsocialapi'"} 43{if="$source !== 'firefoxsocialapi'"}
44{include="page.footer"} 44{include="page.footer"}
45{/if} 45{/if}
46{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"}
47<script src="inc/awesomplete.min.js#"></script> 46<script src="inc/awesomplete.min.js#"></script>
48<script src="inc/awesomplete-multiple-tags.js#"></script> 47<script src="inc/awesomplete-multiple-tags.js#"></script>
49<script> 48<script>
50 awesompleteUniqueTag('#lf_tags'); 49 awesompleteUniqueTag('#lf_tags');
51</script> 50</script>
52{/if}
53</body> 51</body>
54</html> 52</html>
diff --git a/tpl/linklist.html b/tpl/linklist.html
index c0d42006..2316f145 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -88,7 +88,7 @@
88 </span> 88 </span>
89 <br> 89 <br>
90 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} 90 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
91 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} 91 {if="!$hide_timestamps || isLoggedIn()"}
92 <span class="linkdate" title="Permalink"><a href="?{$value.linkdate|smallHash}">{function="strftime('%c', $value.timestamp)"} - permalink</a> - </span> 92 <span class="linkdate" title="Permalink"><a href="?{$value.linkdate|smallHash}">{function="strftime('%c', $value.timestamp)"} - permalink</a> - </span>
93 {else} 93 {else}
94 <span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span> 94 <span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span>
diff --git a/tpl/page.header.html b/tpl/page.header.html
index 3a09ecd9..0012c689 100644
--- a/tpl/page.header.html
+++ b/tpl/page.header.html
@@ -21,14 +21,14 @@
21 <li><a href="?do=logout">Logout</a></li> 21 <li><a href="?do=logout">Logout</a></li>
22 <li><a href="?do=tools">Tools</a></li> 22 <li><a href="?do=tools">Tools</a></li>
23 <li><a href="?do=addlink">Add link</a></li> 23 <li><a href="?do=addlink">Add link</a></li>
24 {elseif="$GLOBALS['config']['OPEN_SHAARLI']"} 24 {elseif="$openshaarli"}
25 <li><a href="?do=tools">Tools</a></li> 25 <li><a href="?do=tools">Tools</a></li>
26 <li><a href="?do=addlink">Add link</a></li> 26 <li><a href="?do=addlink">Add link</a></li>
27 {else} 27 {else}
28 <li><a href="?do=login">Login</a></li> 28 <li><a href="?do=login">Login</a></li>
29 {/if} 29 {/if}
30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li> 30 <li><a href="{$feedurl}?do=rss{$searchcrits}" class="nomobile">RSS Feed</a></li>
31 {if="$GLOBALS['config']['SHOW_ATOM']"} 31 {if="$showatom"}
32 <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li> 32 <li><a href="{$feedurl}?do=atom{$searchcrits}" class="nomobile">ATOM Feed</a></li>
33 {/if} 33 {/if}
34 <li><a href="?do=tagcloud">Tag cloud</a></li> 34 <li><a href="?do=tagcloud">Tag cloud</a></li>
diff --git a/tpl/tools.html b/tpl/tools.html
index 7b4eba09..9e45caad 100644
--- a/tpl/tools.html
+++ b/tpl/tools.html
@@ -9,7 +9,7 @@
9 <br><br> 9 <br><br>
10 <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> 10 <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
11 <br><br> 11 <br><br>
12 {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> 12 {if="$openshaarli"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
13 <br><br>{/if} 13 <br><br>{/if}
14 <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> 14 <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
15 <br><br> 15 <br><br>