3 // FIXME! Namespaces...
4 require_once 'ConfigIO.php';
5 require_once 'ConfigPhp.php';
6 require_once 'ConfigJson.php';
11 * Singleton, manages all Shaarli's settings.
16 * @var ConfigManager instance.
18 protected static $instance = null;
21 * @var string Config folder.
23 public static $CONFIG_FILE = 'data/config';
26 * @var string Flag telling a setting is not found.
28 protected static $NOT_FOUND = 'NOT_FOUND';
31 * @var array Loaded config array.
33 protected $loadedConfig;
36 * @var ConfigIO implementation instance.
41 * Private constructor: new instances not allowed.
43 private function __construct() {}
46 * Cloning isn't allowed either.
48 private function __clone() {}
51 * Return existing instance of PluginManager, or create it.
53 * @return ConfigManager instance.
55 public static function getInstance()
57 if (!(self
::$instance instanceof self
)) {
58 self
::$instance = new self();
59 self
::$instance->initialize();
62 return self
::$instance;
66 * Reset the ConfigManager instance.
68 public static function reset()
70 self
::$instance = null;
71 return self
::getInstance();
75 * Rebuild the loaded config array from config files.
77 public function reload()
83 * Initialize the ConfigIO and loaded the conf.
85 protected function initialize()
87 if (! file_exists(self
::$CONFIG_FILE .'.php')) {
88 $this->configIO
= new ConfigJson();
90 $this->configIO
= new ConfigPhp();
96 * Load configuration in the ConfigurationManager.
98 protected function load()
100 $this->loadedConfig
= $this->configIO
->read($this->getConfigFile());
101 $this->setDefaultValues();
107 * Supports nested settings with dot separated keys.
108 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
110 * { "config": { "stuff": {"option": "mysetting" } } } }
112 * @param string $setting Asked setting, keys separated with dots.
113 * @param string $default Default value if not found.
115 * @return mixed Found setting, or the default value.
117 public function get($setting, $default = '')
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];
124 $settings = explode('.', $setting);
125 $value = self
::getConfig($settings, $this->loadedConfig
);
126 if ($value === self
::$NOT_FOUND) {
133 * Set a setting, and eventually write it.
135 * Supports nested settings with dot separated keys.
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.
142 * @throws Exception Invalid
144 public function set($setting, $value, $write = false, $isLoggedIn = false)
146 if (empty($setting) || ! is_string($setting)) {
147 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
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];
155 $settings = explode('.', $setting);
156 self
::setConfig($settings, $value, $this->loadedConfig
);
158 $this->write($isLoggedIn);
163 * Check if a settings exists.
165 * Supports nested settings with dot separated keys.
167 * @param string $setting Asked setting, keys separated with dots.
169 * @return bool true if the setting exists, false otherwise.
171 public function exists($setting)
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];
178 $settings = explode('.', $setting);
179 $value = self
::getConfig($settings, $this->loadedConfig
);
180 if ($value === self
::$NOT_FOUND) {
187 * Call the config writer.
189 * @param bool $isLoggedIn User login state.
191 * @return bool True if the configuration has been successfully written, false otherwise.
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.
197 public function write($isLoggedIn)
199 // These fields are required in configuration.
200 $mandatoryFields = array(
204 'security.session_protection_disabled',
207 'general.header_link',
208 'general.default_private_links',
212 // Only logged in user can alter config.
213 if (is_file(self
::$CONFIG_FILE) && !$isLoggedIn) {
214 throw new UnauthorizedConfigException();
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);
224 return $this->configIO
->write($this->getConfigFile(), $this->loadedConfig
);
228 * Get the configuration file path.
230 * @return string Config file path.
232 public function getConfigFile()
234 return self
::$CONFIG_FILE . $this->configIO
->getExtension();
238 * Recursive function which find asked setting in the loaded config.
240 * @param array $settings Ordered array which contains keys to find.
241 * @param array $conf Loaded settings, then sub-array.
243 * @return mixed Found setting or NOT_FOUND flag.
245 protected static function getConfig($settings, $conf)
247 if (!is_array($settings) || count($settings) == 0) {
248 return self
::$NOT_FOUND;
251 $setting = array_shift($settings);
252 if (!isset($conf[$setting])) {
253 return self
::$NOT_FOUND;
256 if (count($settings) > 0) {
257 return self
::getConfig($settings, $conf[$setting]);
259 return $conf[$setting];
263 * Recursive function which find asked setting in the loaded config.
265 * @param array $settings Ordered array which contains keys to find.
266 * @param mixed $value
267 * @param array $conf Loaded settings, then sub-array.
269 * @return mixed Found setting or NOT_FOUND flag.
271 protected static function setConfig($settings, $value, &$conf)
273 if (!is_array($settings) || count($settings) == 0) {
274 return self
::$NOT_FOUND;
277 $setting = array_shift($settings);
278 if (count($settings) > 0) {
279 return self
::setConfig($settings, $value, $conf[$setting]);
281 $conf[$setting] = $value;
285 * Set a bunch of default values allowing Shaarli to start without a config file.
287 protected function setDefaultValues()
290 $this->setEmpty('path.data_dir', 'data');
292 // Main configuration file
293 $this->setEmpty('path.config', 'data/config.php');
296 $this->setEmpty('path.datastore', 'data/datastore.php');
299 $this->setEmpty('path.ban_file', 'data/ipbans.php');
301 // Processed updates file.
302 $this->setEmpty('path.updates', 'data/updates.txt');
305 $this->setEmpty('path.log', 'data/log.txt');
307 // For updates check of Shaarli
308 $this->setEmpty('path.update_check', 'data/lastupdatecheck.txt');
310 // Set ENABLE_UPDATECHECK to disabled by default.
311 $this->setEmpty('general.check_updates', false);
313 // RainTPL cache directory (keep the trailing slash!)
314 $this->setEmpty('path.raintpl_tmp', 'tmp/');
315 // Raintpl template directory (keep the trailing slash!)
316 $this->setEmpty('path.raintpl_tpl', 'tpl/');
318 // Thumbnail cache directory
319 $this->setEmpty('path.thumbnails_cache', 'cache');
321 // Atom & RSS feed cache directory
322 $this->setEmpty('path.page_cache', 'pagecache');
324 // Ban IP after this many failures
325 $this->setEmpty('security.ban_after', 4);
326 // Ban duration for IP address after login failures (in seconds)
327 $this->setEmpty('security.ban_after', 1800);
330 // Enable RSS permalinks by default.
331 // This corresponds to the default behavior of shaarli before this was added as an option.
332 $this->setEmpty('general.rss_permalinks', true);
333 // If true, an extra "ATOM feed" button will be displayed in the toolbar
334 $this->setEmpty('extras.show_atom', false);
336 // Link display options
337 $this->setEmpty('extras.hide_public_links', false);
338 $this->setEmpty('extras.hide_timestamps', false);
339 $this->setEmpty('general.links_per_page', 20);
341 // Private checkbox is checked by default
342 $this->setEmpty('general.default_private_links', false);
344 // Open Shaarli (true): anyone can add/edit/delete links without having to login
345 $this->setEmpty('extras.open_shaarli', false);
348 // Display thumbnails in links
349 $this->setEmpty('general.enable_thumbnails', true);
350 // Store thumbnails in a local cache
351 $this->setEmpty('general.enable_localcache', true);
353 // Update check frequency for Shaarli. 86400 seconds=24 hours
354 $this->setEmpty('general.check_updates_branch', 'stable');
355 $this->setEmpty('general.check_updates_interval', 86400);
357 $this->setEmpty('extras.redirector', '');
358 $this->setEmpty('extras.redirector_encode_url', true);
361 $this->setEmpty('general.enabled_plugins', array('qrcode'));
363 // Initialize plugin parameters array.
364 $this->setEmpty('plugins', array());
368 * Set only if the setting does not exists.
370 * @param string $key Setting key.
371 * @param mixed $value Setting value.
373 protected function setEmpty($key, $value)
375 if (! $this->exists($key)) {
376 $this->set($key, $value);
383 public function getConfigIO()
385 return $this->configIO
;
389 * @param ConfigIO $configIO
391 public function setConfigIO($configIO)
393 $this->configIO
= $configIO;
398 * Exception used if a mandatory field is missing in given configuration.
400 class MissingFieldConfigException
extends Exception
405 * Construct exception.
407 * @param string $field field name missing.
409 public function __construct($field)
411 $this->field
= $field;
412 $this->message
= 'Configuration value is required for '. $this->field
;
417 * Exception used if an unauthorized attempt to edit configuration has been made.
419 class UnauthorizedConfigException
extends Exception
422 * Construct exception.
424 public function __construct()
426 $this->message
= 'You are not authorized to alter config.';