aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/config/ConfigManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'application/config/ConfigManager.php')
-rw-r--r--application/config/ConfigManager.php394
1 files changed, 394 insertions, 0 deletions
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
new file mode 100644
index 00000000..f5f753f8
--- /dev/null
+++ b/application/config/ConfigManager.php
@@ -0,0 +1,394 @@
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 * @param string $configFile Configuration file path without extension.
42 */
43 public function __construct($configFile = 'data/config')
44 {
45 $this->configFile = $configFile;
46 $this->initialize();
47 }
48
49 /**
50 * Reset the ConfigManager instance.
51 */
52 public function reset()
53 {
54 $this->initialize();
55 }
56
57 /**
58 * Rebuild the loaded config array from config files.
59 */
60 public function reload()
61 {
62 $this->load();
63 }
64
65 /**
66 * Initialize the ConfigIO and loaded the conf.
67 */
68 protected function initialize()
69 {
70 if (file_exists($this->configFile . '.php')) {
71 $this->configIO = new ConfigPhp();
72 } else {
73 $this->configIO = new ConfigJson();
74 }
75 $this->load();
76 }
77
78 /**
79 * Load configuration in the ConfigurationManager.
80 */
81 protected function load()
82 {
83 $this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
84 $this->setDefaultValues();
85 }
86
87 /**
88 * Get a setting.
89 *
90 * Supports nested settings with dot separated keys.
91 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
92 * or in JSON:
93 * { "config": { "stuff": {"option": "mysetting" } } } }
94 *
95 * @param string $setting Asked setting, keys separated with dots.
96 * @param string $default Default value if not found.
97 *
98 * @return mixed Found setting, or the default value.
99 */
100 public function get($setting, $default = '')
101 {
102 // During the ConfigIO transition, map legacy settings to the new ones.
103 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
104 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
105 }
106
107 $settings = explode('.', $setting);
108 $value = self::getConfig($settings, $this->loadedConfig);
109 if ($value === self::$NOT_FOUND) {
110 return $default;
111 }
112 return $value;
113 }
114
115 /**
116 * Set a setting, and eventually write it.
117 *
118 * Supports nested settings with dot separated keys.
119 *
120 * @param string $setting Asked setting, keys separated with dots.
121 * @param string $value Value to set.
122 * @param bool $write Write the new setting in the config file, default false.
123 * @param bool $isLoggedIn User login state, default false.
124 *
125 * @throws Exception Invalid
126 */
127 public function set($setting, $value, $write = false, $isLoggedIn = false)
128 {
129 if (empty($setting) || ! is_string($setting)) {
130 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
131 }
132
133 // During the ConfigIO transition, map legacy settings to the new ones.
134 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
135 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
136 }
137
138 $settings = explode('.', $setting);
139 self::setConfig($settings, $value, $this->loadedConfig);
140 if ($write) {
141 $this->write($isLoggedIn);
142 }
143 }
144
145 /**
146 * Check if a settings exists.
147 *
148 * Supports nested settings with dot separated keys.
149 *
150 * @param string $setting Asked setting, keys separated with dots.
151 *
152 * @return bool true if the setting exists, false otherwise.
153 */
154 public function exists($setting)
155 {
156 // During the ConfigIO transition, map legacy settings to the new ones.
157 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
158 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
159 }
160
161 $settings = explode('.', $setting);
162 $value = self::getConfig($settings, $this->loadedConfig);
163 if ($value === self::$NOT_FOUND) {
164 return false;
165 }
166 return true;
167 }
168
169 /**
170 * Call the config writer.
171 *
172 * @param bool $isLoggedIn User login state.
173 *
174 * @return bool True if the configuration has been successfully written, false otherwise.
175 *
176 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
177 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
178 * @throws IOException: an error occurred while writing the new config file.
179 */
180 public function write($isLoggedIn)
181 {
182 // These fields are required in configuration.
183 $mandatoryFields = array(
184 'credentials.login',
185 'credentials.hash',
186 'credentials.salt',
187 'security.session_protection_disabled',
188 'general.timezone',
189 'general.title',
190 'general.header_link',
191 'privacy.default_private_links',
192 'redirector.url',
193 );
194
195 // Only logged in user can alter config.
196 if (is_file($this->getConfigFileExt()) && !$isLoggedIn) {
197 throw new UnauthorizedConfigException();
198 }
199
200 // Check that all mandatory fields are provided in $conf.
201 foreach ($mandatoryFields as $field) {
202 if (! $this->exists($field)) {
203 throw new MissingFieldConfigException($field);
204 }
205 }
206
207 return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig);
208 }
209
210 /**
211 * Set the config file path (without extension).
212 *
213 * @param string $configFile File path.
214 */
215 public function setConfigFile($configFile)
216 {
217 $this->configFile = $configFile;
218 }
219
220 /**
221 * Return the configuration file path (without extension).
222 *
223 * @return string Config path.
224 */
225 public function getConfigFile()
226 {
227 return $this->configFile;
228 }
229
230 /**
231 * Get the configuration file path with its extension.
232 *
233 * @return string Config file path.
234 */
235 public function getConfigFileExt()
236 {
237 return $this->configFile . $this->configIO->getExtension();
238 }
239
240 /**
241 * Recursive function which find asked setting in the loaded config.
242 *
243 * @param array $settings Ordered array which contains keys to find.
244 * @param array $conf Loaded settings, then sub-array.
245 *
246 * @return mixed Found setting or NOT_FOUND flag.
247 */
248 protected static function getConfig($settings, $conf)
249 {
250 if (!is_array($settings) || count($settings) == 0) {
251 return self::$NOT_FOUND;
252 }
253
254 $setting = array_shift($settings);
255 if (!isset($conf[$setting])) {
256 return self::$NOT_FOUND;
257 }
258
259 if (count($settings) > 0) {
260 return self::getConfig($settings, $conf[$setting]);
261 }
262 return $conf[$setting];
263 }
264
265 /**
266 * Recursive function which find asked setting in the loaded config.
267 *
268 * @param array $settings Ordered array which contains keys to find.
269 * @param mixed $value
270 * @param array $conf Loaded settings, then sub-array.
271 *
272 * @return mixed Found setting or NOT_FOUND flag.
273 */
274 protected static function setConfig($settings, $value, &$conf)
275 {
276 if (!is_array($settings) || count($settings) == 0) {
277 return self::$NOT_FOUND;
278 }
279
280 $setting = array_shift($settings);
281 if (count($settings) > 0) {
282 return self::setConfig($settings, $value, $conf[$setting]);
283 }
284 $conf[$setting] = $value;
285 }
286
287 /**
288 * Set a bunch of default values allowing Shaarli to start without a config file.
289 */
290 protected function setDefaultValues()
291 {
292 $this->setEmpty('resource.data_dir', 'data');
293 $this->setEmpty('resource.config', 'data/config.php');
294 $this->setEmpty('resource.datastore', 'data/datastore.php');
295 $this->setEmpty('resource.ban_file', 'data/ipbans.php');
296 $this->setEmpty('resource.updates', 'data/updates.txt');
297 $this->setEmpty('resource.log', 'data/log.txt');
298 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
299 $this->setEmpty('resource.raintpl_tpl', 'tpl/');
300 $this->setEmpty('resource.raintpl_tmp', 'tmp/');
301 $this->setEmpty('resource.thumbnails_cache', 'cache');
302 $this->setEmpty('resource.page_cache', 'pagecache');
303
304 $this->setEmpty('security.ban_after', 4);
305 $this->setEmpty('security.ban_duration', 1800);
306 $this->setEmpty('security.session_protection_disabled', false);
307 $this->setEmpty('security.open_shaarli', false);
308
309 $this->setEmpty('general.header_link', '?');
310 $this->setEmpty('general.links_per_page', 20);
311 $this->setEmpty('general.enabled_plugins', array('qrcode'));
312
313 $this->setEmpty('updates.check_updates', false);
314 $this->setEmpty('updates.check_updates_branch', 'stable');
315 $this->setEmpty('updates.check_updates_interval', 86400);
316
317 $this->setEmpty('feed.rss_permalinks', true);
318 $this->setEmpty('feed.show_atom', false);
319
320 $this->setEmpty('privacy.default_private_links', false);
321 $this->setEmpty('privacy.hide_public_links', false);
322 $this->setEmpty('privacy.hide_timestamps', false);
323
324 $this->setEmpty('thumbnail.enable_thumbnails', true);
325 $this->setEmpty('thumbnail.enable_localcache', true);
326
327 $this->setEmpty('redirector.url', '');
328 $this->setEmpty('redirector.encode_url', true);
329
330 $this->setEmpty('plugins', array());
331 }
332
333 /**
334 * Set only if the setting does not exists.
335 *
336 * @param string $key Setting key.
337 * @param mixed $value Setting value.
338 */
339 public function setEmpty($key, $value)
340 {
341 if (! $this->exists($key)) {
342 $this->set($key, $value);
343 }
344 }
345
346 /**
347 * @return ConfigIO
348 */
349 public function getConfigIO()
350 {
351 return $this->configIO;
352 }
353
354 /**
355 * @param ConfigIO $configIO
356 */
357 public function setConfigIO($configIO)
358 {
359 $this->configIO = $configIO;
360 }
361}
362
363/**
364 * Exception used if a mandatory field is missing in given configuration.
365 */
366class MissingFieldConfigException extends Exception
367{
368 public $field;
369
370 /**
371 * Construct exception.
372 *
373 * @param string $field field name missing.
374 */
375 public function __construct($field)
376 {
377 $this->field = $field;
378 $this->message = 'Configuration value is required for '. $this->field;
379 }
380}
381
382/**
383 * Exception used if an unauthorized attempt to edit configuration has been made.
384 */
385class UnauthorizedConfigException extends Exception
386{
387 /**
388 * Construct exception.
389 */
390 public function __construct()
391 {
392 $this->message = 'You are not authorized to alter config.';
393 }
394}