diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/config/ConfigIO.php | 33 | ||||
-rw-r--r-- | application/config/ConfigManager.php | 363 | ||||
-rw-r--r-- | application/config/ConfigPhp.php | 93 | ||||
-rw-r--r-- | application/config/ConfigPlugin.php | 118 |
4 files changed, 607 insertions, 0 deletions
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 | */ | ||
8 | interface 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/ConfigManager.php b/application/config/ConfigManager.php new file mode 100644 index 00000000..dfe9eeb9 --- /dev/null +++ b/application/config/ConfigManager.php | |||
@@ -0,0 +1,363 @@ | |||
1 | <?php | ||
2 | |||
3 | // FIXME! Namespaces... | ||
4 | require_once 'ConfigIO.php'; | ||
5 | require_once 'ConfigPhp.php'; | ||
6 | #require_once 'ConfigJson.php'; | ||
7 | |||
8 | /** | ||
9 | * Class ConfigManager | ||
10 | * | ||
11 | * Singleton, manages all Shaarli's settings. | ||
12 | */ | ||
13 | class ConfigManager | ||
14 | { | ||
15 | /** | ||
16 | * @var ConfigManager instance. | ||
17 | */ | ||
18 | protected static $instance = null; | ||
19 | |||
20 | /** | ||
21 | * @var string Config folder. | ||
22 | */ | ||
23 | public static $CONFIG_FILE = 'data/config'; | ||
24 | |||
25 | /** | ||
26 | * @var string Flag telling a setting is not found. | ||
27 | */ | ||
28 | protected static $NOT_FOUND = 'NOT_FOUND'; | ||
29 | |||
30 | /** | ||
31 | * @var array Loaded config array. | ||
32 | */ | ||
33 | protected $loadedConfig; | ||
34 | |||
35 | /** | ||
36 | * @var ConfigIO implementation instance. | ||
37 | */ | ||
38 | protected $configIO; | ||
39 | |||
40 | /** | ||
41 | * Private constructor: new instances not allowed. | ||
42 | */ | ||
43 | private function __construct() {} | ||
44 | |||
45 | /** | ||
46 | * Cloning isn't allowed either. | ||
47 | */ | ||
48 | private function __clone() {} | ||
49 | |||
50 | /** | ||
51 | * Return existing instance of PluginManager, or create it. | ||
52 | * | ||
53 | * @return ConfigManager instance. | ||
54 | */ | ||
55 | public static function getInstance() | ||
56 | { | ||
57 | if (!(self::$instance instanceof self)) { | ||
58 | self::$instance = new self(); | ||
59 | self::$instance->initialize(); | ||
60 | } | ||
61 | |||
62 | return self::$instance; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Rebuild the loaded config array from config files. | ||
67 | */ | ||
68 | public function reload() | ||
69 | { | ||
70 | $this->initialize(); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * Initialize loaded conf in ConfigManager. | ||
75 | */ | ||
76 | protected function initialize() | ||
77 | { | ||
78 | /*if (! file_exists(self::$CONFIG_FILE .'.php')) { | ||
79 | $this->configIO = new ConfigJson(); | ||
80 | } else { | ||
81 | $this->configIO = new ConfigPhp(); | ||
82 | }*/ | ||
83 | $this->configIO = new ConfigPhp(); | ||
84 | $this->loadedConfig = $this->configIO->read(self::$CONFIG_FILE); | ||
85 | $this->setDefaultValues(); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Get a setting. | ||
90 | * | ||
91 | * Supports nested settings with dot separated keys. | ||
92 | * Eg. 'config.stuff.option' will find $conf[config][stuff][option], | ||
93 | * or in JSON: | ||
94 | * { "config": { "stuff": {"option": "mysetting" } } } } | ||
95 | * | ||
96 | * @param string $setting Asked setting, keys separated with dots. | ||
97 | * @param string $default Default value if not found. | ||
98 | * | ||
99 | * @return mixed Found setting, or the default value. | ||
100 | */ | ||
101 | public function get($setting, $default = '') | ||
102 | { | ||
103 | $settings = explode('.', $setting); | ||
104 | $value = self::getConfig($settings, $this->loadedConfig); | ||
105 | if ($value === self::$NOT_FOUND) { | ||
106 | return $default; | ||
107 | } | ||
108 | return $value; | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Set a setting, and eventually write it. | ||
113 | * | ||
114 | * Supports nested settings with dot separated keys. | ||
115 | * | ||
116 | * @param string $setting Asked setting, keys separated with dots. | ||
117 | * @param string $value Value to set. | ||
118 | * @param bool $write Write the new setting in the config file, default false. | ||
119 | * @param bool $isLoggedIn User login state, default false. | ||
120 | */ | ||
121 | public function set($setting, $value, $write = false, $isLoggedIn = false) | ||
122 | { | ||
123 | $settings = explode('.', $setting); | ||
124 | self::setConfig($settings, $value, $this->loadedConfig); | ||
125 | if ($write) { | ||
126 | $this->write($isLoggedIn); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * Check if a settings exists. | ||
132 | * | ||
133 | * Supports nested settings with dot separated keys. | ||
134 | * | ||
135 | * @param string $setting Asked setting, keys separated with dots. | ||
136 | * | ||
137 | * @return bool true if the setting exists, false otherwise. | ||
138 | */ | ||
139 | public function exists($setting) | ||
140 | { | ||
141 | $settings = explode('.', $setting); | ||
142 | $value = self::getConfig($settings, $this->loadedConfig); | ||
143 | if ($value === self::$NOT_FOUND) { | ||
144 | return false; | ||
145 | } | ||
146 | return true; | ||
147 | } | ||
148 | |||
149 | /** | ||
150 | * Call the config writer. | ||
151 | * | ||
152 | * @param bool $isLoggedIn User login state. | ||
153 | * | ||
154 | * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf. | ||
155 | * @throws UnauthorizedConfigException: user is not authorize to change configuration. | ||
156 | * @throws IOException: an error occurred while writing the new config file. | ||
157 | */ | ||
158 | public function write($isLoggedIn) | ||
159 | { | ||
160 | // These fields are required in configuration. | ||
161 | $mandatoryFields = array( | ||
162 | 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink', | ||
163 | 'redirector', 'disablesessionprotection', 'privateLinkByDefault' | ||
164 | ); | ||
165 | |||
166 | // Only logged in user can alter config. | ||
167 | if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) { | ||
168 | throw new UnauthorizedConfigException(); | ||
169 | } | ||
170 | |||
171 | // Check that all mandatory fields are provided in $conf. | ||
172 | foreach ($mandatoryFields as $field) { | ||
173 | if (! $this->exists($field)) { | ||
174 | throw new MissingFieldConfigException($field); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | $this->configIO->write(self::$CONFIG_FILE, $this->loadedConfig); | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * Get the configuration file path. | ||
183 | * | ||
184 | * @return string Config file path. | ||
185 | */ | ||
186 | public function getConfigFile() | ||
187 | { | ||
188 | return self::$CONFIG_FILE . $this->configIO->getExtension(); | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Recursive function which find asked setting in the loaded config. | ||
193 | * | ||
194 | * @param array $settings Ordered array which contains keys to find. | ||
195 | * @param array $conf Loaded settings, then sub-array. | ||
196 | * | ||
197 | * @return mixed Found setting or NOT_FOUND flag. | ||
198 | */ | ||
199 | protected static function getConfig($settings, $conf) | ||
200 | { | ||
201 | if (!is_array($settings) || count($settings) == 0) { | ||
202 | return self::$NOT_FOUND; | ||
203 | } | ||
204 | |||
205 | $setting = array_shift($settings); | ||
206 | if (!isset($conf[$setting])) { | ||
207 | return self::$NOT_FOUND; | ||
208 | } | ||
209 | |||
210 | if (count($settings) > 0) { | ||
211 | return self::getConfig($settings, $conf[$setting]); | ||
212 | } | ||
213 | return $conf[$setting]; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Recursive function which find asked setting in the loaded config. | ||
218 | * | ||
219 | * @param array $settings Ordered array which contains keys to find. | ||
220 | * @param mixed $value | ||
221 | * @param array $conf Loaded settings, then sub-array. | ||
222 | * | ||
223 | * @return mixed Found setting or NOT_FOUND flag. | ||
224 | */ | ||
225 | protected static function setConfig($settings, $value, &$conf) | ||
226 | { | ||
227 | if (!is_array($settings) || count($settings) == 0) { | ||
228 | return self::$NOT_FOUND; | ||
229 | } | ||
230 | |||
231 | $setting = array_shift($settings); | ||
232 | if (count($settings) > 0) { | ||
233 | return self::setConfig($settings, $value, $conf[$setting]); | ||
234 | } | ||
235 | $conf[$setting] = $value; | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * Set a bunch of default values allowing Shaarli to start without a config file. | ||
240 | */ | ||
241 | protected function setDefaultValues() | ||
242 | { | ||
243 | // Data subdirectory | ||
244 | $this->setEmpty('config.DATADIR', 'data'); | ||
245 | |||
246 | // Main configuration file | ||
247 | $this->setEmpty('config.CONFIG_FILE', 'data/config.php'); | ||
248 | |||
249 | // Link datastore | ||
250 | $this->setEmpty('config.DATASTORE', 'data/datastore.php'); | ||
251 | |||
252 | // Banned IPs | ||
253 | $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php'); | ||
254 | |||
255 | // Processed updates file. | ||
256 | $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt'); | ||
257 | |||
258 | // Access log | ||
259 | $this->setEmpty('config.LOG_FILE', 'data/log.txt'); | ||
260 | |||
261 | // For updates check of Shaarli | ||
262 | $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt'); | ||
263 | |||
264 | // Set ENABLE_UPDATECHECK to disabled by default. | ||
265 | $this->setEmpty('config.ENABLE_UPDATECHECK', false); | ||
266 | |||
267 | // RainTPL cache directory (keep the trailing slash!) | ||
268 | $this->setEmpty('config.RAINTPL_TMP', 'tmp/'); | ||
269 | // Raintpl template directory (keep the trailing slash!) | ||
270 | $this->setEmpty('config.RAINTPL_TPL', 'tpl/'); | ||
271 | |||
272 | // Thumbnail cache directory | ||
273 | $this->setEmpty('config.CACHEDIR', 'cache'); | ||
274 | |||
275 | // Atom & RSS feed cache directory | ||
276 | $this->setEmpty('config.PAGECACHE', 'pagecache'); | ||
277 | |||
278 | // Ban IP after this many failures | ||
279 | $this->setEmpty('config.BAN_AFTER', 4); | ||
280 | // Ban duration for IP address after login failures (in seconds) | ||
281 | $this->setEmpty('config.BAN_DURATION', 1800); | ||
282 | |||
283 | // Feed options | ||
284 | // Enable RSS permalinks by default. | ||
285 | // This corresponds to the default behavior of shaarli before this was added as an option. | ||
286 | $this->setEmpty('config.ENABLE_RSS_PERMALINKS', true); | ||
287 | // If true, an extra "ATOM feed" button will be displayed in the toolbar | ||
288 | $this->setEmpty('config.SHOW_ATOM', false); | ||
289 | |||
290 | // Link display options | ||
291 | $this->setEmpty('config.HIDE_PUBLIC_LINKS', false); | ||
292 | $this->setEmpty('config.HIDE_TIMESTAMPS', false); | ||
293 | $this->setEmpty('config.LINKS_PER_PAGE', 20); | ||
294 | |||
295 | // Open Shaarli (true): anyone can add/edit/delete links without having to login | ||
296 | $this->setEmpty('config.OPEN_SHAARLI', false); | ||
297 | |||
298 | // Thumbnails | ||
299 | // Display thumbnails in links | ||
300 | $this->setEmpty('config.ENABLE_THUMBNAILS', true); | ||
301 | // Store thumbnails in a local cache | ||
302 | $this->setEmpty('config.ENABLE_LOCALCACHE', true); | ||
303 | |||
304 | // Update check frequency for Shaarli. 86400 seconds=24 hours | ||
305 | $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable'); | ||
306 | $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400); | ||
307 | |||
308 | $this->setEmpty('redirector', ''); | ||
309 | $this->setEmpty('config.REDIRECTOR_URLENCODE', true); | ||
310 | |||
311 | // Enabled plugins. | ||
312 | $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode')); | ||
313 | |||
314 | // Initialize plugin parameters array. | ||
315 | $this->setEmpty('plugins', array()); | ||
316 | } | ||
317 | |||
318 | /** | ||
319 | * Set only if the setting does not exists. | ||
320 | * | ||
321 | * @param string $key Setting key. | ||
322 | * @param mixed $value Setting value. | ||
323 | */ | ||
324 | protected function setEmpty($key, $value) | ||
325 | { | ||
326 | if (! $this->exists($key)) { | ||
327 | $this->set($key, $value); | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | |||
332 | /** | ||
333 | * Exception used if a mandatory field is missing in given configuration. | ||
334 | */ | ||
335 | class MissingFieldConfigException extends Exception | ||
336 | { | ||
337 | public $field; | ||
338 | |||
339 | /** | ||
340 | * Construct exception. | ||
341 | * | ||
342 | * @param string $field field name missing. | ||
343 | */ | ||
344 | public function __construct($field) | ||
345 | { | ||
346 | $this->field = $field; | ||
347 | $this->message = 'Configuration value is required for '. $this->field; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | /** | ||
352 | * Exception used if an unauthorized attempt to edit configuration has been made. | ||
353 | */ | ||
354 | class UnauthorizedConfigException extends Exception | ||
355 | { | ||
356 | /** | ||
357 | * Construct exception. | ||
358 | */ | ||
359 | public function __construct() | ||
360 | { | ||
361 | $this->message = 'You are not authorized to alter config.'; | ||
362 | } | ||
363 | } | ||
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php new file mode 100644 index 00000000..311aeb81 --- /dev/null +++ b/application/config/ConfigPhp.php | |||
@@ -0,0 +1,93 @@ | |||
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 | */ | ||
9 | class 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 | * @inheritdoc | ||
28 | */ | ||
29 | function read($filepath) | ||
30 | { | ||
31 | $filepath .= $this->getExtension(); | ||
32 | if (! file_exists($filepath) || ! is_readable($filepath)) { | ||
33 | return array(); | ||
34 | } | ||
35 | |||
36 | include $filepath; | ||
37 | |||
38 | $out = array(); | ||
39 | foreach (self::$ROOT_KEYS as $key) { | ||
40 | $out[$key] = $GLOBALS[$key]; | ||
41 | } | ||
42 | $out['config'] = $GLOBALS['config']; | ||
43 | $out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array(); | ||
44 | return $out; | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * @inheritdoc | ||
49 | */ | ||
50 | function write($filepath, $conf) | ||
51 | { | ||
52 | $filepath .= $this->getExtension(); | ||
53 | |||
54 | $configStr = '<?php '. PHP_EOL; | ||
55 | foreach (self::$ROOT_KEYS as $key) { | ||
56 | if (isset($conf[$key])) { | ||
57 | $configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | // Store all $conf['config'] | ||
62 | foreach ($conf['config'] as $key => $value) { | ||
63 | $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL; | ||
64 | } | ||
65 | |||
66 | if (isset($conf['plugins'])) { | ||
67 | foreach ($conf['plugins'] as $key => $value) { | ||
68 | $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | // FIXME! | ||
73 | //$configStr .= 'date_default_timezone_set('.var_export($conf['timezone'], true).');'. PHP_EOL; | ||
74 | |||
75 | if (!file_put_contents($filepath, $configStr) | ||
76 | || strcmp(file_get_contents($filepath), $configStr) != 0 | ||
77 | ) { | ||
78 | throw new IOException( | ||
79 | $filepath, | ||
80 | 'Shaarli could not create the config file. | ||
81 | Please make sure Shaarli has the right to write in the folder is it installed in.' | ||
82 | ); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * @inheritdoc | ||
88 | */ | ||
89 | function getExtension() | ||
90 | { | ||
91 | return '.php'; | ||
92 | } | ||
93 | } | ||
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php new file mode 100644 index 00000000..8af89d04 --- /dev/null +++ b/application/config/ConfigPlugin.php | |||
@@ -0,0 +1,118 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Functions related to configuration management. | ||
4 | */ | ||
5 | |||
6 | /** | ||
7 | * Process plugin administration form data and save it in an array. | ||
8 | * | ||
9 | * @param array $formData Data sent by the plugin admin form. | ||
10 | * | ||
11 | * @return array New list of enabled plugin, ordered. | ||
12 | * | ||
13 | * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid. | ||
14 | */ | ||
15 | function save_plugin_config($formData) | ||
16 | { | ||
17 | // Make sure there are no duplicates in orders. | ||
18 | if (!validate_plugin_order($formData)) { | ||
19 | throw new PluginConfigOrderException(); | ||
20 | } | ||
21 | |||
22 | $plugins = array(); | ||
23 | $newEnabledPlugins = array(); | ||
24 | foreach ($formData as $key => $data) { | ||
25 | if (startsWith($key, 'order')) { | ||
26 | continue; | ||
27 | } | ||
28 | |||
29 | // If there is no order, it means a disabled plugin has been enabled. | ||
30 | if (isset($formData['order_' . $key])) { | ||
31 | $plugins[(int) $formData['order_' . $key]] = $key; | ||
32 | } | ||
33 | else { | ||
34 | $newEnabledPlugins[] = $key; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | // New enabled plugins will be added at the end of order. | ||
39 | $plugins = array_merge($plugins, $newEnabledPlugins); | ||
40 | |||
41 | // Sort plugins by order. | ||
42 | if (!ksort($plugins)) { | ||
43 | throw new PluginConfigOrderException(); | ||
44 | } | ||
45 | |||
46 | $finalPlugins = array(); | ||
47 | // Make plugins order continuous. | ||
48 | foreach ($plugins as $plugin) { | ||
49 | $finalPlugins[] = $plugin; | ||
50 | } | ||
51 | |||
52 | return $finalPlugins; | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * Validate plugin array submitted. | ||
57 | * Will fail if there is duplicate orders value. | ||
58 | * | ||
59 | * @param array $formData Data from submitted form. | ||
60 | * | ||
61 | * @return bool true if ok, false otherwise. | ||
62 | */ | ||
63 | function validate_plugin_order($formData) | ||
64 | { | ||
65 | $orders = array(); | ||
66 | foreach ($formData as $key => $value) { | ||
67 | // No duplicate order allowed. | ||
68 | if (in_array($value, $orders)) { | ||
69 | return false; | ||
70 | } | ||
71 | |||
72 | if (startsWith($key, 'order')) { | ||
73 | $orders[] = $value; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | return true; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Affect plugin parameters values into plugins array. | ||
82 | * | ||
83 | * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>. | ||
84 | * @param mixed $conf Plugins configuration. | ||
85 | * | ||
86 | * @return mixed Updated $plugins array. | ||
87 | */ | ||
88 | function load_plugin_parameter_values($plugins, $conf) | ||
89 | { | ||
90 | $out = $plugins; | ||
91 | foreach ($plugins as $name => $plugin) { | ||
92 | if (empty($plugin['parameters'])) { | ||
93 | continue; | ||
94 | } | ||
95 | |||
96 | foreach ($plugin['parameters'] as $key => $param) { | ||
97 | if (!empty($conf[$key])) { | ||
98 | $out[$name]['parameters'][$key] = $conf[$key]; | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | return $out; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Exception used if an error occur while saving plugin configuration. | ||
108 | */ | ||
109 | class PluginConfigOrderException extends Exception | ||
110 | { | ||
111 | /** | ||
112 | * Construct exception. | ||
113 | */ | ||
114 | public function __construct() | ||
115 | { | ||
116 | $this->message = 'An error occurred while trying to save plugins loading order.'; | ||
117 | } | ||
118 | } | ||