]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - application/config/ConfigManager.php
Rename configuration keys and fix GLOBALS in templates
[github/shaarli/Shaarli.git] / application / config / ConfigManager.php
CommitLineData
59404d79
A
1<?php
2
3// FIXME! Namespaces...
4require_once 'ConfigIO.php';
5require_once 'ConfigPhp.php';
b74b96bf 6require_once 'ConfigJson.php';
59404d79
A
7
8/**
9 * Class ConfigManager
10 *
11 * Singleton, manages all Shaarli's settings.
12 */
13class 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
684e662a
A
65 /**
66 * Reset the ConfigManager instance.
67 */
68 public static function reset()
69 {
70 self::$instance = null;
71 return self::getInstance();
72 }
73
59404d79
A
74 /**
75 * Rebuild the loaded config array from config files.
76 */
77 public function reload()
78 {
684e662a 79 $this->load();
59404d79
A
80 }
81
82 /**
684e662a 83 * Initialize the ConfigIO and loaded the conf.
59404d79
A
84 */
85 protected function initialize()
86 {
b74b96bf 87 if (! file_exists(self::$CONFIG_FILE .'.php')) {
59404d79
A
88 $this->configIO = new ConfigJson();
89 } else {
90 $this->configIO = new ConfigPhp();
b74b96bf 91 }
684e662a
A
92 $this->load();
93 }
94
95 /**
96 * Load configuration in the ConfigurationManager.
97 */
98 protected function load()
99 {
100 $this->loadedConfig = $this->configIO->read($this->getConfigFile());
59404d79
A
101 $this->setDefaultValues();
102 }
103
104 /**
105 * Get a setting.
106 *
107 * Supports nested settings with dot separated keys.
108 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
109 * or in JSON:
110 * { "config": { "stuff": {"option": "mysetting" } } } }
111 *
112 * @param string $setting Asked setting, keys separated with dots.
113 * @param string $default Default value if not found.
114 *
115 * @return mixed Found setting, or the default value.
116 */
117 public function get($setting, $default = '')
118 {
da10377b
A
119 // During the ConfigIO transition, map legacy settings to the new ones.
120 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
121 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
122 }
123
59404d79
A
124 $settings = explode('.', $setting);
125 $value = self::getConfig($settings, $this->loadedConfig);
126 if ($value === self::$NOT_FOUND) {
127 return $default;
128 }
129 return $value;
130 }
131
132 /**
133 * Set a setting, and eventually write it.
134 *
135 * Supports nested settings with dot separated keys.
136 *
137 * @param string $setting Asked setting, keys separated with dots.
138 * @param string $value Value to set.
139 * @param bool $write Write the new setting in the config file, default false.
140 * @param bool $isLoggedIn User login state, default false.
684e662a
A
141 *
142 * @throws Exception Invalid
59404d79
A
143 */
144 public function set($setting, $value, $write = false, $isLoggedIn = false)
145 {
684e662a
A
146 if (empty($setting) || ! is_string($setting)) {
147 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
148 }
149
da10377b
A
150 // During the ConfigIO transition, map legacy settings to the new ones.
151 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
152 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
153 }
154
59404d79
A
155 $settings = explode('.', $setting);
156 self::setConfig($settings, $value, $this->loadedConfig);
157 if ($write) {
158 $this->write($isLoggedIn);
159 }
160 }
161
162 /**
163 * Check if a settings exists.
164 *
165 * Supports nested settings with dot separated keys.
166 *
167 * @param string $setting Asked setting, keys separated with dots.
168 *
169 * @return bool true if the setting exists, false otherwise.
170 */
171 public function exists($setting)
172 {
da10377b
A
173 // During the ConfigIO transition, map legacy settings to the new ones.
174 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
175 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
176 }
177
59404d79
A
178 $settings = explode('.', $setting);
179 $value = self::getConfig($settings, $this->loadedConfig);
180 if ($value === self::$NOT_FOUND) {
181 return false;
182 }
183 return true;
184 }
185
186 /**
187 * Call the config writer.
188 *
189 * @param bool $isLoggedIn User login state.
190 *
684e662a
A
191 * @return bool True if the configuration has been successfully written, false otherwise.
192 *
59404d79
A
193 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
194 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
195 * @throws IOException: an error occurred while writing the new config file.
196 */
197 public function write($isLoggedIn)
198 {
199 // These fields are required in configuration.
200 $mandatoryFields = array(
da10377b
A
201 'credentials.login',
202 'credentials.hash',
203 'credentials.salt',
204 'security.session_protection_disabled',
205 'general.timezone',
206 'general.title',
207 'general.header_link',
208 'general.default_private_links',
209 'extras.redirector',
59404d79
A
210 );
211
212 // Only logged in user can alter config.
213 if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) {
214 throw new UnauthorizedConfigException();
215 }
216
217 // Check that all mandatory fields are provided in $conf.
218 foreach ($mandatoryFields as $field) {
219 if (! $this->exists($field)) {
220 throw new MissingFieldConfigException($field);
221 }
222 }
223
684e662a 224 return $this->configIO->write($this->getConfigFile(), $this->loadedConfig);
59404d79
A
225 }
226
227 /**
228 * Get the configuration file path.
229 *
230 * @return string Config file path.
231 */
232 public function getConfigFile()
233 {
234 return self::$CONFIG_FILE . $this->configIO->getExtension();
235 }
236
237 /**
238 * Recursive function which find asked setting in the loaded config.
239 *
240 * @param array $settings Ordered array which contains keys to find.
241 * @param array $conf Loaded settings, then sub-array.
242 *
243 * @return mixed Found setting or NOT_FOUND flag.
244 */
245 protected static function getConfig($settings, $conf)
246 {
247 if (!is_array($settings) || count($settings) == 0) {
248 return self::$NOT_FOUND;
249 }
250
251 $setting = array_shift($settings);
252 if (!isset($conf[$setting])) {
253 return self::$NOT_FOUND;
254 }
255
256 if (count($settings) > 0) {
257 return self::getConfig($settings, $conf[$setting]);
258 }
259 return $conf[$setting];
260 }
261
262 /**
263 * Recursive function which find asked setting in the loaded config.
264 *
265 * @param array $settings Ordered array which contains keys to find.
266 * @param mixed $value
267 * @param array $conf Loaded settings, then sub-array.
268 *
269 * @return mixed Found setting or NOT_FOUND flag.
270 */
271 protected static function setConfig($settings, $value, &$conf)
272 {
273 if (!is_array($settings) || count($settings) == 0) {
274 return self::$NOT_FOUND;
275 }
276
277 $setting = array_shift($settings);
278 if (count($settings) > 0) {
279 return self::setConfig($settings, $value, $conf[$setting]);
280 }
281 $conf[$setting] = $value;
282 }
283
284 /**
285 * Set a bunch of default values allowing Shaarli to start without a config file.
286 */
287 protected function setDefaultValues()
288 {
289 // Data subdirectory
da10377b 290 $this->setEmpty('path.data_dir', 'data');
59404d79
A
291
292 // Main configuration file
da10377b 293 $this->setEmpty('path.config', 'data/config.php');
59404d79
A
294
295 // Link datastore
da10377b 296 $this->setEmpty('path.datastore', 'data/datastore.php');
59404d79
A
297
298 // Banned IPs
da10377b 299 $this->setEmpty('path.ban_file', 'data/ipbans.php');
59404d79
A
300
301 // Processed updates file.
da10377b 302 $this->setEmpty('path.updates', 'data/updates.txt');
59404d79
A
303
304 // Access log
da10377b 305 $this->setEmpty('path.log', 'data/log.txt');
59404d79
A
306
307 // For updates check of Shaarli
da10377b 308 $this->setEmpty('path.update_check', 'data/lastupdatecheck.txt');
59404d79
A
309
310 // Set ENABLE_UPDATECHECK to disabled by default.
da10377b 311 $this->setEmpty('general.check_updates', false);
59404d79
A
312
313 // RainTPL cache directory (keep the trailing slash!)
da10377b 314 $this->setEmpty('path.raintpl_tmp', 'tmp/');
59404d79 315 // Raintpl template directory (keep the trailing slash!)
da10377b 316 $this->setEmpty('path.raintpl_tpl', 'tpl/');
59404d79
A
317
318 // Thumbnail cache directory
da10377b 319 $this->setEmpty('path.thumbnails_cache', 'cache');
59404d79
A
320
321 // Atom & RSS feed cache directory
da10377b 322 $this->setEmpty('path.page_cache', 'pagecache');
59404d79
A
323
324 // Ban IP after this many failures
da10377b 325 $this->setEmpty('security.ban_after', 4);
59404d79 326 // Ban duration for IP address after login failures (in seconds)
da10377b 327 $this->setEmpty('security.ban_after', 1800);
59404d79
A
328
329 // Feed options
330 // Enable RSS permalinks by default.
331 // This corresponds to the default behavior of shaarli before this was added as an option.
da10377b 332 $this->setEmpty('general.rss_permalinks', true);
59404d79 333 // If true, an extra "ATOM feed" button will be displayed in the toolbar
da10377b 334 $this->setEmpty('extras.show_atom', false);
59404d79
A
335
336 // Link display options
da10377b
A
337 $this->setEmpty('extras.hide_public_links', false);
338 $this->setEmpty('extras.hide_timestamps', false);
339 $this->setEmpty('general.links_per_page', 20);
340
341 // Private checkbox is checked by default
342 $this->setEmpty('general.default_private_links', false);
59404d79
A
343
344 // Open Shaarli (true): anyone can add/edit/delete links without having to login
da10377b 345 $this->setEmpty('extras.open_shaarli', false);
59404d79
A
346
347 // Thumbnails
348 // Display thumbnails in links
da10377b 349 $this->setEmpty('general.enable_thumbnails', true);
59404d79 350 // Store thumbnails in a local cache
da10377b 351 $this->setEmpty('general.enable_localcache', true);
59404d79
A
352
353 // Update check frequency for Shaarli. 86400 seconds=24 hours
da10377b
A
354 $this->setEmpty('general.check_updates_branch', 'stable');
355 $this->setEmpty('general.check_updates_interval', 86400);
59404d79 356
da10377b
A
357 $this->setEmpty('extras.redirector', '');
358 $this->setEmpty('extras.redirector_encode_url', true);
59404d79
A
359
360 // Enabled plugins.
da10377b 361 $this->setEmpty('general.enabled_plugins', array('qrcode'));
59404d79
A
362
363 // Initialize plugin parameters array.
364 $this->setEmpty('plugins', array());
365 }
366
367 /**
368 * Set only if the setting does not exists.
369 *
370 * @param string $key Setting key.
371 * @param mixed $value Setting value.
372 */
373 protected function setEmpty($key, $value)
374 {
375 if (! $this->exists($key)) {
376 $this->set($key, $value);
377 }
378 }
684e662a
A
379
380 /**
381 * @return ConfigIO
382 */
383 public function getConfigIO()
384 {
385 return $this->configIO;
386 }
387
388 /**
389 * @param ConfigIO $configIO
390 */
391 public function setConfigIO($configIO)
392 {
393 $this->configIO = $configIO;
394 }
59404d79
A
395}
396
397/**
398 * Exception used if a mandatory field is missing in given configuration.
399 */
400class MissingFieldConfigException extends Exception
401{
402 public $field;
403
404 /**
405 * Construct exception.
406 *
407 * @param string $field field name missing.
408 */
409 public function __construct($field)
410 {
411 $this->field = $field;
412 $this->message = 'Configuration value is required for '. $this->field;
413 }
414}
415
416/**
417 * Exception used if an unauthorized attempt to edit configuration has been made.
418 */
419class UnauthorizedConfigException extends Exception
420{
421 /**
422 * Construct exception.
423 */
424 public function __construct()
425 {
426 $this->message = 'You are not authorized to alter config.';
427 }
428}