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 * Rebuild the loaded config array from config files.
68 public function reload()
74 * Initialize loaded conf in ConfigManager.
76 protected function initialize()
78 /*if (! file_exists(self::$CONFIG_FILE .'.php')) {
79 $this->configIO = new ConfigJson();
81 $this->configIO = new ConfigPhp();
83 $this->configIO
= new ConfigPhp();
84 $this->loadedConfig
= $this->configIO
->read(self
::$CONFIG_FILE);
85 $this->setDefaultValues();
91 * Supports nested settings with dot separated keys.
92 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
94 * { "config": { "stuff": {"option": "mysetting" } } } }
96 * @param string $setting Asked setting, keys separated with dots.
97 * @param string $default Default value if not found.
99 * @return mixed Found setting, or the default value.
101 public function get($setting, $default = '')
103 $settings = explode('.', $setting);
104 $value = self
::getConfig($settings, $this->loadedConfig
);
105 if ($value === self
::$NOT_FOUND) {
112 * Set a setting, and eventually write it.
114 * Supports nested settings with dot separated keys.
116 * @param string $setting Asked setting, keys separated with dots.
117 * @param string $value Value to set.
118 * @param bool $write Write the new setting in the config file, default false.
119 * @param bool $isLoggedIn User login state, default false.
121 public function set($setting, $value, $write = false, $isLoggedIn = false)
123 $settings = explode('.', $setting);
124 self
::setConfig($settings, $value, $this->loadedConfig
);
126 $this->write($isLoggedIn);
131 * Check if a settings exists.
133 * Supports nested settings with dot separated keys.
135 * @param string $setting Asked setting, keys separated with dots.
137 * @return bool true if the setting exists, false otherwise.
139 public function exists($setting)
141 $settings = explode('.', $setting);
142 $value = self
::getConfig($settings, $this->loadedConfig
);
143 if ($value === self
::$NOT_FOUND) {
150 * Call the config writer.
152 * @param bool $isLoggedIn User login state.
154 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
155 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
156 * @throws IOException: an error occurred while writing the new config file.
158 public function write($isLoggedIn)
160 // These fields are required in configuration.
161 $mandatoryFields = array(
162 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
163 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
166 // Only logged in user can alter config.
167 if (is_file(self
::$CONFIG_FILE) && !$isLoggedIn) {
168 throw new UnauthorizedConfigException();
171 // Check that all mandatory fields are provided in $conf.
172 foreach ($mandatoryFields as $field) {
173 if (! $this->exists($field)) {
174 throw new MissingFieldConfigException($field);
178 $this->configIO
->write(self
::$CONFIG_FILE, $this->loadedConfig
);
182 * Get the configuration file path.
184 * @return string Config file path.
186 public function getConfigFile()
188 return self
::$CONFIG_FILE . $this->configIO
->getExtension();
192 * Recursive function which find asked setting in the loaded config.
194 * @param array $settings Ordered array which contains keys to find.
195 * @param array $conf Loaded settings, then sub-array.
197 * @return mixed Found setting or NOT_FOUND flag.
199 protected static function getConfig($settings, $conf)
201 if (!is_array($settings) || count($settings) == 0) {
202 return self
::$NOT_FOUND;
205 $setting = array_shift($settings);
206 if (!isset($conf[$setting])) {
207 return self
::$NOT_FOUND;
210 if (count($settings) > 0) {
211 return self
::getConfig($settings, $conf[$setting]);
213 return $conf[$setting];
217 * Recursive function which find asked setting in the loaded config.
219 * @param array $settings Ordered array which contains keys to find.
220 * @param mixed $value
221 * @param array $conf Loaded settings, then sub-array.
223 * @return mixed Found setting or NOT_FOUND flag.
225 protected static function setConfig($settings, $value, &$conf)
227 if (!is_array($settings) || count($settings) == 0) {
228 return self
::$NOT_FOUND;
231 $setting = array_shift($settings);
232 if (count($settings) > 0) {
233 return self
::setConfig($settings, $value, $conf[$setting]);
235 $conf[$setting] = $value;
239 * Set a bunch of default values allowing Shaarli to start without a config file.
241 protected function setDefaultValues()
244 $this->setEmpty('config.DATADIR', 'data');
246 // Main configuration file
247 $this->setEmpty('config.CONFIG_FILE', 'data/config.php');
250 $this->setEmpty('config.DATASTORE', 'data/datastore.php');
253 $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php');
255 // Processed updates file.
256 $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt');
259 $this->setEmpty('config.LOG_FILE', 'data/log.txt');
261 // For updates check of Shaarli
262 $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt');
264 // Set ENABLE_UPDATECHECK to disabled by default.
265 $this->setEmpty('config.ENABLE_UPDATECHECK', false);
267 // RainTPL cache directory (keep the trailing slash!)
268 $this->setEmpty('config.RAINTPL_TMP', 'tmp/');
269 // Raintpl template directory (keep the trailing slash!)
270 $this->setEmpty('config.RAINTPL_TPL', 'tpl/');
272 // Thumbnail cache directory
273 $this->setEmpty('config.CACHEDIR', 'cache');
275 // Atom & RSS feed cache directory
276 $this->setEmpty('config.PAGECACHE', 'pagecache');
278 // Ban IP after this many failures
279 $this->setEmpty('config.BAN_AFTER', 4);
280 // Ban duration for IP address after login failures (in seconds)
281 $this->setEmpty('config.BAN_DURATION', 1800);
284 // Enable RSS permalinks by default.
285 // This corresponds to the default behavior of shaarli before this was added as an option.
286 $this->setEmpty('config.ENABLE_RSS_PERMALINKS', true);
287 // If true, an extra "ATOM feed" button will be displayed in the toolbar
288 $this->setEmpty('config.SHOW_ATOM', false);
290 // Link display options
291 $this->setEmpty('config.HIDE_PUBLIC_LINKS', false);
292 $this->setEmpty('config.HIDE_TIMESTAMPS', false);
293 $this->setEmpty('config.LINKS_PER_PAGE', 20);
295 // Open Shaarli (true): anyone can add/edit/delete links without having to login
296 $this->setEmpty('config.OPEN_SHAARLI', false);
299 // Display thumbnails in links
300 $this->setEmpty('config.ENABLE_THUMBNAILS', true);
301 // Store thumbnails in a local cache
302 $this->setEmpty('config.ENABLE_LOCALCACHE', true);
304 // Update check frequency for Shaarli. 86400 seconds=24 hours
305 $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable');
306 $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400);
308 $this->setEmpty('redirector', '');
309 $this->setEmpty('config.REDIRECTOR_URLENCODE', true);
312 $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode'));
314 // Initialize plugin parameters array.
315 $this->setEmpty('plugins', array());
319 * Set only if the setting does not exists.
321 * @param string $key Setting key.
322 * @param mixed $value Setting value.
324 protected function setEmpty($key, $value)
326 if (! $this->exists($key)) {
327 $this->set($key, $value);
333 * Exception used if a mandatory field is missing in given configuration.
335 class MissingFieldConfigException
extends Exception
340 * Construct exception.
342 * @param string $field field name missing.
344 public function __construct($field)
346 $this->field
= $field;
347 $this->message
= 'Configuration value is required for '. $this->field
;
352 * Exception used if an unauthorized attempt to edit configuration has been made.
354 class UnauthorizedConfigException
extends Exception
357 * Construct exception.
359 public function __construct()
361 $this->message
= 'You are not authorized to alter config.';