]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - application/config/ConfigManager.php
Remove remaining settings initialization in index.php
[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.
7f179985
A
12 * See the documentation for more information on settings:
13 * - doc/Shaarli-configuration.html
14 * - https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration
59404d79
A
15 */
16class ConfigManager
17{
18 /**
19 * @var ConfigManager instance.
20 */
21 protected static $instance = null;
22
23 /**
24 * @var string Config folder.
25 */
26 public static $CONFIG_FILE = 'data/config';
27
28 /**
29 * @var string Flag telling a setting is not found.
30 */
31 protected static $NOT_FOUND = 'NOT_FOUND';
32
33 /**
34 * @var array Loaded config array.
35 */
36 protected $loadedConfig;
37
38 /**
39 * @var ConfigIO implementation instance.
40 */
41 protected $configIO;
42
43 /**
44 * Private constructor: new instances not allowed.
45 */
46 private function __construct() {}
47
48 /**
49 * Cloning isn't allowed either.
50 */
51 private function __clone() {}
52
53 /**
54 * Return existing instance of PluginManager, or create it.
55 *
56 * @return ConfigManager instance.
57 */
58 public static function getInstance()
59 {
60 if (!(self::$instance instanceof self)) {
61 self::$instance = new self();
62 self::$instance->initialize();
63 }
64
65 return self::$instance;
66 }
67
684e662a
A
68 /**
69 * Reset the ConfigManager instance.
70 */
71 public static function reset()
72 {
73 self::$instance = null;
74 return self::getInstance();
75 }
76
59404d79
A
77 /**
78 * Rebuild the loaded config array from config files.
79 */
80 public function reload()
81 {
684e662a 82 $this->load();
59404d79
A
83 }
84
85 /**
684e662a 86 * Initialize the ConfigIO and loaded the conf.
59404d79
A
87 */
88 protected function initialize()
89 {
b74b96bf 90 if (! file_exists(self::$CONFIG_FILE .'.php')) {
59404d79
A
91 $this->configIO = new ConfigJson();
92 } else {
93 $this->configIO = new ConfigPhp();
b74b96bf 94 }
684e662a
A
95 $this->load();
96 }
97
98 /**
99 * Load configuration in the ConfigurationManager.
100 */
101 protected function load()
102 {
103 $this->loadedConfig = $this->configIO->read($this->getConfigFile());
59404d79
A
104 $this->setDefaultValues();
105 }
106
107 /**
108 * Get a setting.
109 *
110 * Supports nested settings with dot separated keys.
111 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
112 * or in JSON:
113 * { "config": { "stuff": {"option": "mysetting" } } } }
114 *
115 * @param string $setting Asked setting, keys separated with dots.
116 * @param string $default Default value if not found.
117 *
118 * @return mixed Found setting, or the default value.
119 */
120 public function get($setting, $default = '')
121 {
da10377b
A
122 // During the ConfigIO transition, map legacy settings to the new ones.
123 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
124 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
125 }
126
59404d79
A
127 $settings = explode('.', $setting);
128 $value = self::getConfig($settings, $this->loadedConfig);
129 if ($value === self::$NOT_FOUND) {
130 return $default;
131 }
132 return $value;
133 }
134
135 /**
136 * Set a setting, and eventually write it.
137 *
138 * Supports nested settings with dot separated keys.
139 *
140 * @param string $setting Asked setting, keys separated with dots.
141 * @param string $value Value to set.
142 * @param bool $write Write the new setting in the config file, default false.
143 * @param bool $isLoggedIn User login state, default false.
684e662a
A
144 *
145 * @throws Exception Invalid
59404d79
A
146 */
147 public function set($setting, $value, $write = false, $isLoggedIn = false)
148 {
684e662a
A
149 if (empty($setting) || ! is_string($setting)) {
150 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
151 }
152
da10377b
A
153 // During the ConfigIO transition, map legacy settings to the new ones.
154 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
155 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
156 }
157
59404d79
A
158 $settings = explode('.', $setting);
159 self::setConfig($settings, $value, $this->loadedConfig);
160 if ($write) {
161 $this->write($isLoggedIn);
162 }
163 }
164
165 /**
166 * Check if a settings exists.
167 *
168 * Supports nested settings with dot separated keys.
169 *
170 * @param string $setting Asked setting, keys separated with dots.
171 *
172 * @return bool true if the setting exists, false otherwise.
173 */
174 public function exists($setting)
175 {
da10377b
A
176 // During the ConfigIO transition, map legacy settings to the new ones.
177 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
178 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
179 }
180
59404d79
A
181 $settings = explode('.', $setting);
182 $value = self::getConfig($settings, $this->loadedConfig);
183 if ($value === self::$NOT_FOUND) {
184 return false;
185 }
186 return true;
187 }
188
189 /**
190 * Call the config writer.
191 *
192 * @param bool $isLoggedIn User login state.
193 *
684e662a
A
194 * @return bool True if the configuration has been successfully written, false otherwise.
195 *
59404d79
A
196 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
197 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
198 * @throws IOException: an error occurred while writing the new config file.
199 */
200 public function write($isLoggedIn)
201 {
202 // These fields are required in configuration.
203 $mandatoryFields = array(
da10377b
A
204 'credentials.login',
205 'credentials.hash',
206 'credentials.salt',
207 'security.session_protection_disabled',
208 'general.timezone',
209 'general.title',
210 'general.header_link',
211 'general.default_private_links',
212 'extras.redirector',
59404d79
A
213 );
214
215 // Only logged in user can alter config.
216 if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) {
217 throw new UnauthorizedConfigException();
218 }
219
220 // Check that all mandatory fields are provided in $conf.
221 foreach ($mandatoryFields as $field) {
222 if (! $this->exists($field)) {
223 throw new MissingFieldConfigException($field);
224 }
225 }
226
684e662a 227 return $this->configIO->write($this->getConfigFile(), $this->loadedConfig);
59404d79
A
228 }
229
230 /**
231 * Get the configuration file path.
232 *
233 * @return string Config file path.
234 */
235 public function getConfigFile()
236 {
237 return self::$CONFIG_FILE . $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 {
da10377b 292 $this->setEmpty('path.data_dir', 'data');
da10377b 293 $this->setEmpty('path.config', 'data/config.php');
da10377b 294 $this->setEmpty('path.datastore', 'data/datastore.php');
da10377b 295 $this->setEmpty('path.ban_file', 'data/ipbans.php');
da10377b 296 $this->setEmpty('path.updates', 'data/updates.txt');
da10377b 297 $this->setEmpty('path.log', 'data/log.txt');
da10377b 298 $this->setEmpty('path.update_check', 'data/lastupdatecheck.txt');
da10377b 299 $this->setEmpty('path.raintpl_tpl', 'tpl/');
7f179985 300 $this->setEmpty('path.raintpl_tmp', 'tmp/');
da10377b 301 $this->setEmpty('path.thumbnails_cache', 'cache');
da10377b 302 $this->setEmpty('path.page_cache', 'pagecache');
59404d79 303
da10377b 304 $this->setEmpty('security.ban_after', 4);
da10377b 305 $this->setEmpty('security.ban_after', 1800);
7f179985 306 $this->setEmpty('security.session_protection_disabled', false);
59404d79 307
7f179985 308 $this->setEmpty('general.check_updates', false);
da10377b 309 $this->setEmpty('general.rss_permalinks', true);
da10377b 310 $this->setEmpty('general.links_per_page', 20);
da10377b 311 $this->setEmpty('general.default_private_links', false);
da10377b 312 $this->setEmpty('general.enable_thumbnails', true);
da10377b 313 $this->setEmpty('general.enable_localcache', true);
da10377b
A
314 $this->setEmpty('general.check_updates_branch', 'stable');
315 $this->setEmpty('general.check_updates_interval', 86400);
7f179985
A
316 $this->setEmpty('general.header_link', '?');
317 $this->setEmpty('general.enabled_plugins', array('qrcode'));
59404d79 318
7f179985
A
319 $this->setEmpty('extras.show_atom', false);
320 $this->setEmpty('extras.hide_public_links', false);
321 $this->setEmpty('extras.hide_timestamps', false);
322 $this->setEmpty('extras.open_shaarli', false);
da10377b
A
323 $this->setEmpty('extras.redirector', '');
324 $this->setEmpty('extras.redirector_encode_url', true);
59404d79 325
59404d79
A
326 $this->setEmpty('plugins', array());
327 }
328
329 /**
330 * Set only if the setting does not exists.
331 *
332 * @param string $key Setting key.
333 * @param mixed $value Setting value.
334 */
7f179985 335 public function setEmpty($key, $value)
59404d79
A
336 {
337 if (! $this->exists($key)) {
338 $this->set($key, $value);
339 }
340 }
684e662a
A
341
342 /**
343 * @return ConfigIO
344 */
345 public function getConfigIO()
346 {
347 return $this->configIO;
348 }
349
350 /**
351 * @param ConfigIO $configIO
352 */
353 public function setConfigIO($configIO)
354 {
355 $this->configIO = $configIO;
356 }
59404d79
A
357}
358
359/**
360 * Exception used if a mandatory field is missing in given configuration.
361 */
362class MissingFieldConfigException extends Exception
363{
364 public $field;
365
366 /**
367 * Construct exception.
368 *
369 * @param string $field field name missing.
370 */
371 public function __construct($field)
372 {
373 $this->field = $field;
374 $this->message = 'Configuration value is required for '. $this->field;
375 }
376}
377
378/**
379 * Exception used if an unauthorized attempt to edit configuration has been made.
380 */
381class UnauthorizedConfigException extends Exception
382{
383 /**
384 * Construct exception.
385 */
386 public function __construct()
387 {
388 $this->message = 'You are not authorized to alter config.';
389 }
390}