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 $settings = explode('.', $setting);
120 $value = self
::getConfig($settings, $this->loadedConfig
);
121 if ($value === self
::$NOT_FOUND) {
128 * Set a setting, and eventually write it.
130 * Supports nested settings with dot separated keys.
132 * @param string $setting Asked setting, keys separated with dots.
133 * @param string $value Value to set.
134 * @param bool $write Write the new setting in the config file, default false.
135 * @param bool $isLoggedIn User login state, default false.
137 * @throws Exception Invalid
139 public function set($setting, $value, $write = false, $isLoggedIn = false)
141 if (empty($setting) || ! is_string($setting)) {
142 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
145 $settings = explode('.', $setting);
146 self
::setConfig($settings, $value, $this->loadedConfig
);
148 $this->write($isLoggedIn);
153 * Check if a settings exists.
155 * Supports nested settings with dot separated keys.
157 * @param string $setting Asked setting, keys separated with dots.
159 * @return bool true if the setting exists, false otherwise.
161 public function exists($setting)
163 $settings = explode('.', $setting);
164 $value = self
::getConfig($settings, $this->loadedConfig
);
165 if ($value === self
::$NOT_FOUND) {
172 * Call the config writer.
174 * @param bool $isLoggedIn User login state.
176 * @return bool True if the configuration has been successfully written, false otherwise.
178 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
179 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
180 * @throws IOException: an error occurred while writing the new config file.
182 public function write($isLoggedIn)
184 // These fields are required in configuration.
185 $mandatoryFields = array(
186 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
187 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
190 // Only logged in user can alter config.
191 if (is_file(self
::$CONFIG_FILE) && !$isLoggedIn) {
192 throw new UnauthorizedConfigException();
195 // Check that all mandatory fields are provided in $conf.
196 foreach ($mandatoryFields as $field) {
197 if (! $this->exists($field)) {
198 throw new MissingFieldConfigException($field);
202 return $this->configIO
->write($this->getConfigFile(), $this->loadedConfig
);
206 * Get the configuration file path.
208 * @return string Config file path.
210 public function getConfigFile()
212 return self
::$CONFIG_FILE . $this->configIO
->getExtension();
216 * Recursive function which find asked setting in the loaded config.
218 * @param array $settings Ordered array which contains keys to find.
219 * @param array $conf Loaded settings, then sub-array.
221 * @return mixed Found setting or NOT_FOUND flag.
223 protected static function getConfig($settings, $conf)
225 if (!is_array($settings) || count($settings) == 0) {
226 return self
::$NOT_FOUND;
229 $setting = array_shift($settings);
230 if (!isset($conf[$setting])) {
231 return self
::$NOT_FOUND;
234 if (count($settings) > 0) {
235 return self
::getConfig($settings, $conf[$setting]);
237 return $conf[$setting];
241 * Recursive function which find asked setting in the loaded config.
243 * @param array $settings Ordered array which contains keys to find.
244 * @param mixed $value
245 * @param array $conf Loaded settings, then sub-array.
247 * @return mixed Found setting or NOT_FOUND flag.
249 protected static function setConfig($settings, $value, &$conf)
251 if (!is_array($settings) || count($settings) == 0) {
252 return self
::$NOT_FOUND;
255 $setting = array_shift($settings);
256 if (count($settings) > 0) {
257 return self
::setConfig($settings, $value, $conf[$setting]);
259 $conf[$setting] = $value;
263 * Set a bunch of default values allowing Shaarli to start without a config file.
265 protected function setDefaultValues()
268 $this->setEmpty('config.DATADIR', 'data');
270 // Main configuration file
271 $this->setEmpty('config.CONFIG_FILE', 'data/config.php');
274 $this->setEmpty('config.DATASTORE', 'data/datastore.php');
277 $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php');
279 // Processed updates file.
280 $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt');
283 $this->setEmpty('config.LOG_FILE', 'data/log.txt');
285 // For updates check of Shaarli
286 $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt');
288 // Set ENABLE_UPDATECHECK to disabled by default.
289 $this->setEmpty('config.ENABLE_UPDATECHECK', false);
291 // RainTPL cache directory (keep the trailing slash!)
292 $this->setEmpty('config.RAINTPL_TMP', 'tmp/');
293 // Raintpl template directory (keep the trailing slash!)
294 $this->setEmpty('config.RAINTPL_TPL', 'tpl/');
296 // Thumbnail cache directory
297 $this->setEmpty('config.CACHEDIR', 'cache');
299 // Atom & RSS feed cache directory
300 $this->setEmpty('config.PAGECACHE', 'pagecache');
302 // Ban IP after this many failures
303 $this->setEmpty('config.BAN_AFTER', 4);
304 // Ban duration for IP address after login failures (in seconds)
305 $this->setEmpty('config.BAN_DURATION', 1800);
308 // Enable RSS permalinks by default.
309 // This corresponds to the default behavior of shaarli before this was added as an option.
310 $this->setEmpty('config.ENABLE_RSS_PERMALINKS', true);
311 // If true, an extra "ATOM feed" button will be displayed in the toolbar
312 $this->setEmpty('config.SHOW_ATOM', false);
314 // Link display options
315 $this->setEmpty('config.HIDE_PUBLIC_LINKS', false);
316 $this->setEmpty('config.HIDE_TIMESTAMPS', false);
317 $this->setEmpty('config.LINKS_PER_PAGE', 20);
319 // Open Shaarli (true): anyone can add/edit/delete links without having to login
320 $this->setEmpty('config.OPEN_SHAARLI', false);
323 // Display thumbnails in links
324 $this->setEmpty('config.ENABLE_THUMBNAILS', true);
325 // Store thumbnails in a local cache
326 $this->setEmpty('config.ENABLE_LOCALCACHE', true);
328 // Update check frequency for Shaarli. 86400 seconds=24 hours
329 $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable');
330 $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400);
332 $this->setEmpty('redirector', '');
333 $this->setEmpty('config.REDIRECTOR_URLENCODE', true);
336 $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode'));
338 // Initialize plugin parameters array.
339 $this->setEmpty('plugins', array());
343 * Set only if the setting does not exists.
345 * @param string $key Setting key.
346 * @param mixed $value Setting value.
348 protected function setEmpty($key, $value)
350 if (! $this->exists($key)) {
351 $this->set($key, $value);
358 public function getConfigIO()
360 return $this->configIO
;
364 * @param ConfigIO $configIO
366 public function setConfigIO($configIO)
368 $this->configIO
= $configIO;
373 * Exception used if a mandatory field is missing in given configuration.
375 class MissingFieldConfigException
extends Exception
380 * Construct exception.
382 * @param string $field field name missing.
384 public function __construct($field)
386 $this->field
= $field;
387 $this->message
= 'Configuration value is required for '. $this->field
;
392 * Exception used if an unauthorized attempt to edit configuration has been made.
394 class UnauthorizedConfigException
extends Exception
397 * Construct exception.
399 public function __construct()
401 $this->message
= 'You are not authorized to alter config.';