]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - application/config/ConfigManager.php
Apply PHP Code Beautifier on source code for linter automatic fixes
[github/shaarli/Shaarli.git] / application / config / ConfigManager.php
CommitLineData
59404d79 1<?php
53054b2b 2
3c66e564 3namespace Shaarli\Config;
59404d79 4
5ba55f0c
A
5use Shaarli\Config\Exception\MissingFieldConfigException;
6use Shaarli\Config\Exception\UnauthorizedConfigException;
3ee8351e 7use Shaarli\Thumbnailer;
5ba55f0c 8
59404d79
A
9/**
10 * Class ConfigManager
11 *
278d9ee2 12 * Manages all Shaarli's settings.
7f179985 13 * See the documentation for more information on settings:
cc8f572b
WE
14 * - doc/md/Shaarli-configuration.md
15 * - https://shaarli.readthedocs.io/en/master/Shaarli-configuration/#configuration
59404d79
A
16 */
17class ConfigManager
18{
19 /**
278d9ee2 20 * @var string Flag telling a setting is not found.
59404d79 21 */
278d9ee2 22 protected static $NOT_FOUND = 'NOT_FOUND';
59404d79 23
53054b2b 24 public static $DEFAULT_PLUGINS = ['qrcode'];
18e67967 25
59404d79
A
26 /**
27 * @var string Config folder.
28 */
278d9ee2 29 protected $configFile;
59404d79
A
30
31 /**
32 * @var array Loaded config array.
33 */
34 protected $loadedConfig;
35
36 /**
37 * @var ConfigIO implementation instance.
38 */
39 protected $configIO;
40
41 /**
278d9ee2 42 * Constructor.
7af9a418
A
43 *
44 * @param string $configFile Configuration file path without extension.
59404d79 45 */
278d9ee2 46 public function __construct($configFile = 'data/config')
59404d79 47 {
278d9ee2
A
48 $this->configFile = $configFile;
49 $this->initialize();
59404d79
A
50 }
51
684e662a
A
52 /**
53 * Reset the ConfigManager instance.
54 */
278d9ee2 55 public function reset()
684e662a 56 {
278d9ee2 57 $this->initialize();
684e662a
A
58 }
59
59404d79
A
60 /**
61 * Rebuild the loaded config array from config files.
62 */
63 public function reload()
64 {
684e662a 65 $this->load();
59404d79
A
66 }
67
68 /**
684e662a 69 * Initialize the ConfigIO and loaded the conf.
59404d79
A
70 */
71 protected function initialize()
72 {
278d9ee2 73 if (file_exists($this->configFile . '.php')) {
59404d79 74 $this->configIO = new ConfigPhp();
278d9ee2
A
75 } else {
76 $this->configIO = new ConfigJson();
b74b96bf 77 }
684e662a
A
78 $this->load();
79 }
80
81 /**
82 * Load configuration in the ConfigurationManager.
83 */
84 protected function load()
85 {
c6a4c288
A
86 try {
87 $this->loadedConfig = $this->configIO->read($this->getConfigFileExt());
88 } catch (\Exception $e) {
89 die($e->getMessage());
90 }
59404d79
A
91 $this->setDefaultValues();
92 }
93
94 /**
95 * Get a setting.
96 *
97 * Supports nested settings with dot separated keys.
98 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
99 * or in JSON:
100 * { "config": { "stuff": {"option": "mysetting" } } } }
101 *
102 * @param string $setting Asked setting, keys separated with dots.
103 * @param string $default Default value if not found.
104 *
105 * @return mixed Found setting, or the default value.
106 */
107 public function get($setting, $default = '')
108 {
da10377b
A
109 // During the ConfigIO transition, map legacy settings to the new ones.
110 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
111 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
112 }
113
59404d79
A
114 $settings = explode('.', $setting);
115 $value = self::getConfig($settings, $this->loadedConfig);
116 if ($value === self::$NOT_FOUND) {
117 return $default;
118 }
119 return $value;
120 }
121
122 /**
123 * Set a setting, and eventually write it.
124 *
125 * Supports nested settings with dot separated keys.
126 *
127 * @param string $setting Asked setting, keys separated with dots.
980efd6c 128 * @param mixed $value Value to set.
59404d79
A
129 * @param bool $write Write the new setting in the config file, default false.
130 * @param bool $isLoggedIn User login state, default false.
684e662a 131 *
3c66e564 132 * @throws \Exception Invalid
59404d79
A
133 */
134 public function set($setting, $value, $write = false, $isLoggedIn = false)
135 {
684e662a 136 if (empty($setting) || ! is_string($setting)) {
53054b2b 137 throw new \Exception(t('Invalid setting key parameter. String expected, got: ') . gettype($setting));
684e662a
A
138 }
139
da10377b
A
140 // During the ConfigIO transition, map legacy settings to the new ones.
141 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
142 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
143 }
144
59404d79
A
145 $settings = explode('.', $setting);
146 self::setConfig($settings, $value, $this->loadedConfig);
147 if ($write) {
148 $this->write($isLoggedIn);
149 }
150 }
151
a3724717
A
152 /**
153 * Remove a config element from the config file.
154 *
155 * @param string $setting Asked setting, keys separated with dots.
156 * @param bool $write Write the new setting in the config file, default false.
157 * @param bool $isLoggedIn User login state, default false.
158 *
159 * @throws \Exception Invalid
160 */
161 public function remove($setting, $write = false, $isLoggedIn = false)
162 {
163 if (empty($setting) || ! is_string($setting)) {
53054b2b 164 throw new \Exception(t('Invalid setting key parameter. String expected, got: ') . gettype($setting));
a3724717
A
165 }
166
167 // During the ConfigIO transition, map legacy settings to the new ones.
168 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
169 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
170 }
171
172 $settings = explode('.', $setting);
173 self::removeConfig($settings, $this->loadedConfig);
174 if ($write) {
175 $this->write($isLoggedIn);
176 }
177 }
178
59404d79
A
179 /**
180 * Check if a settings exists.
181 *
182 * Supports nested settings with dot separated keys.
183 *
184 * @param string $setting Asked setting, keys separated with dots.
185 *
186 * @return bool true if the setting exists, false otherwise.
187 */
188 public function exists($setting)
189 {
da10377b
A
190 // During the ConfigIO transition, map legacy settings to the new ones.
191 if ($this->configIO instanceof ConfigPhp && isset(ConfigPhp::$LEGACY_KEYS_MAPPING[$setting])) {
192 $setting = ConfigPhp::$LEGACY_KEYS_MAPPING[$setting];
193 }
194
59404d79
A
195 $settings = explode('.', $setting);
196 $value = self::getConfig($settings, $this->loadedConfig);
197 if ($value === self::$NOT_FOUND) {
198 return false;
199 }
200 return true;
201 }
202
203 /**
204 * Call the config writer.
205 *
206 * @param bool $isLoggedIn User login state.
207 *
684e662a
A
208 * @return bool True if the configuration has been successfully written, false otherwise.
209 *
59404d79
A
210 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
211 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
f3d2f257 212 * @throws \Shaarli\Exceptions\IOException: an error occurred while writing the new config file.
59404d79
A
213 */
214 public function write($isLoggedIn)
215 {
216 // These fields are required in configuration.
53054b2b 217 $mandatoryFields = [
da10377b
A
218 'credentials.login',
219 'credentials.hash',
220 'credentials.salt',
221 'security.session_protection_disabled',
222 'general.timezone',
223 'general.title',
224 'general.header_link',
894a3c4b 225 'privacy.default_private_links',
53054b2b 226 ];
59404d79
A
227
228 // Only logged in user can alter config.
278d9ee2 229 if (is_file($this->getConfigFileExt()) && !$isLoggedIn) {
59404d79
A
230 throw new UnauthorizedConfigException();
231 }
232
233 // Check that all mandatory fields are provided in $conf.
234 foreach ($mandatoryFields as $field) {
235 if (! $this->exists($field)) {
236 throw new MissingFieldConfigException($field);
237 }
238 }
239
278d9ee2 240 return $this->configIO->write($this->getConfigFileExt(), $this->loadedConfig);
59404d79
A
241 }
242
243 /**
278d9ee2 244 * Set the config file path (without extension).
59404d79 245 *
278d9ee2
A
246 * @param string $configFile File path.
247 */
248 public function setConfigFile($configFile)
249 {
250 $this->configFile = $configFile;
251 }
252
253 /**
254 * Return the configuration file path (without extension).
255 *
256 * @return string Config path.
59404d79
A
257 */
258 public function getConfigFile()
259 {
278d9ee2
A
260 return $this->configFile;
261 }
262
263 /**
264 * Get the configuration file path with its extension.
265 *
266 * @return string Config file path.
267 */
268 public function getConfigFileExt()
269 {
270 return $this->configFile . $this->configIO->getExtension();
59404d79
A
271 }
272
273 /**
274 * Recursive function which find asked setting in the loaded config.
275 *
276 * @param array $settings Ordered array which contains keys to find.
277 * @param array $conf Loaded settings, then sub-array.
278 *
279 * @return mixed Found setting or NOT_FOUND flag.
280 */
281 protected static function getConfig($settings, $conf)
282 {
283 if (!is_array($settings) || count($settings) == 0) {
284 return self::$NOT_FOUND;
285 }
286
287 $setting = array_shift($settings);
288 if (!isset($conf[$setting])) {
289 return self::$NOT_FOUND;
290 }
291
292 if (count($settings) > 0) {
293 return self::getConfig($settings, $conf[$setting]);
294 }
295 return $conf[$setting];
296 }
297
298 /**
299 * Recursive function which find asked setting in the loaded config.
300 *
301 * @param array $settings Ordered array which contains keys to find.
302 * @param mixed $value
a3724717 303 * @param array $conf Loaded settings, then sub-array.
59404d79
A
304 *
305 * @return mixed Found setting or NOT_FOUND flag.
306 */
307 protected static function setConfig($settings, $value, &$conf)
308 {
309 if (!is_array($settings) || count($settings) == 0) {
310 return self::$NOT_FOUND;
311 }
312
313 $setting = array_shift($settings);
314 if (count($settings) > 0) {
315 return self::setConfig($settings, $value, $conf[$setting]);
316 }
317 $conf[$setting] = $value;
318 }
319
a3724717
A
320 /**
321 * Recursive function which find asked setting in the loaded config and deletes it.
322 *
323 * @param array $settings Ordered array which contains keys to find.
324 * @param array $conf Loaded settings, then sub-array.
325 *
326 * @return mixed Found setting or NOT_FOUND flag.
327 */
328 protected static function removeConfig($settings, &$conf)
329 {
330 if (!is_array($settings) || count($settings) == 0) {
331 return self::$NOT_FOUND;
332 }
333
334 $setting = array_shift($settings);
335 if (count($settings) > 0) {
336 return self::removeConfig($settings, $conf[$setting]);
337 }
338 unset($conf[$setting]);
339 }
340
59404d79
A
341 /**
342 * Set a bunch of default values allowing Shaarli to start without a config file.
343 */
344 protected function setDefaultValues()
345 {
894a3c4b
A
346 $this->setEmpty('resource.data_dir', 'data');
347 $this->setEmpty('resource.config', 'data/config.php');
348 $this->setEmpty('resource.datastore', 'data/datastore.php');
349 $this->setEmpty('resource.ban_file', 'data/ipbans.php');
350 $this->setEmpty('resource.updates', 'data/updates.txt');
351 $this->setEmpty('resource.log', 'data/log.txt');
352 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
4306b184 353 $this->setEmpty('resource.history', 'data/history.php');
894a3c4b 354 $this->setEmpty('resource.raintpl_tpl', 'tpl/');
adc4aee8 355 $this->setEmpty('resource.theme', 'default');
894a3c4b
A
356 $this->setEmpty('resource.raintpl_tmp', 'tmp/');
357 $this->setEmpty('resource.thumbnails_cache', 'cache');
358 $this->setEmpty('resource.page_cache', 'pagecache');
59404d79 359
da10377b 360 $this->setEmpty('security.ban_after', 4);
278d9ee2 361 $this->setEmpty('security.ban_duration', 1800);
7f179985 362 $this->setEmpty('security.session_protection_disabled', false);
894a3c4b 363 $this->setEmpty('security.open_shaarli', false);
86ceea05 364 $this->setEmpty('security.allowed_protocols', ['ftp', 'ftps', 'magnet']);
59404d79 365
3ee8351e 366 $this->setEmpty('general.header_link', '/');
894a3c4b 367 $this->setEmpty('general.links_per_page', 20);
18e67967 368 $this->setEmpty('general.enabled_plugins', self::$DEFAULT_PLUGINS);
722caa20 369 $this->setEmpty('general.default_note_title', 'Note: ');
4cf3564d
A
370 $this->setEmpty('general.retrieve_description', true);
371 $this->setEmpty('general.enable_async_metadata', true);
b3bd8c3e 372 $this->setEmpty('general.tags_separator', ' ');
59404d79 373
894a3c4b
A
374 $this->setEmpty('updates.check_updates', false);
375 $this->setEmpty('updates.check_updates_branch', 'stable');
376 $this->setEmpty('updates.check_updates_interval', 86400);
377
378 $this->setEmpty('feed.rss_permalinks', true);
2ea89aba 379 $this->setEmpty('feed.show_atom', true);
894a3c4b
A
380
381 $this->setEmpty('privacy.default_private_links', false);
382 $this->setEmpty('privacy.hide_public_links', false);
27e21231 383 $this->setEmpty('privacy.force_login', false);
894a3c4b 384 $this->setEmpty('privacy.hide_timestamps', false);
2e07e775
WE
385 // default state of the 'remember me' checkbox of the login form
386 $this->setEmpty('privacy.remember_user_default', true);
894a3c4b 387
3ee8351e 388 $this->setEmpty('thumbnails.mode', Thumbnailer::MODE_ALL);
7b4fea0e
A
389 $this->setEmpty('thumbnails.width', '125');
390 $this->setEmpty('thumbnails.height', '90');
391
12266213
A
392 $this->setEmpty('translation.language', 'auto');
393 $this->setEmpty('translation.mode', 'php');
394 $this->setEmpty('translation.extensions', []);
395
53054b2b 396 $this->setEmpty('plugins', []);
cf92b4dd
A
397
398 $this->setEmpty('formatter', 'markdown');
59404d79
A
399 }
400
401 /**
402 * Set only if the setting does not exists.
403 *
404 * @param string $key Setting key.
405 * @param mixed $value Setting value.
406 */
7f179985 407 public function setEmpty($key, $value)
59404d79
A
408 {
409 if (! $this->exists($key)) {
410 $this->set($key, $value);
411 }
412 }
684e662a
A
413
414 /**
415 * @return ConfigIO
416 */
417 public function getConfigIO()
418 {
419 return $this->configIO;
420 }
421
422 /**
423 * @param ConfigIO $configIO
424 */
425 public function setConfigIO($configIO)
426 {
427 $this->configIO = $configIO;
428 }
59404d79 429}