]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/config/ConfigManager.php
Replace $GLOBALS configuration with the configuration manager in the whole code base
[github/shaarli/Shaarli.git] / application / config / ConfigManager.php
1 <?php
2
3 // FIXME! Namespaces...
4 require_once 'ConfigIO.php';
5 require_once 'ConfigPhp.php';
6 #require_once 'ConfigJson.php';
7
8 /**
9 * Class ConfigManager
10 *
11 * Singleton, manages all Shaarli's settings.
12 */
13 class ConfigManager
14 {
15 /**
16 * @var ConfigManager instance.
17 */
18 protected static $instance = null;
19
20 /**
21 * @var string Config folder.
22 */
23 public static $CONFIG_FILE = 'data/config';
24
25 /**
26 * @var string Flag telling a setting is not found.
27 */
28 protected static $NOT_FOUND = 'NOT_FOUND';
29
30 /**
31 * @var array Loaded config array.
32 */
33 protected $loadedConfig;
34
35 /**
36 * @var ConfigIO implementation instance.
37 */
38 protected $configIO;
39
40 /**
41 * Private constructor: new instances not allowed.
42 */
43 private function __construct() {}
44
45 /**
46 * Cloning isn't allowed either.
47 */
48 private function __clone() {}
49
50 /**
51 * Return existing instance of PluginManager, or create it.
52 *
53 * @return ConfigManager instance.
54 */
55 public static function getInstance()
56 {
57 if (!(self::$instance instanceof self)) {
58 self::$instance = new self();
59 self::$instance->initialize();
60 }
61
62 return self::$instance;
63 }
64
65 /**
66 * Reset the ConfigManager instance.
67 */
68 public static function reset()
69 {
70 self::$instance = null;
71 return self::getInstance();
72 }
73
74 /**
75 * Rebuild the loaded config array from config files.
76 */
77 public function reload()
78 {
79 $this->load();
80 }
81
82 /**
83 * Initialize the ConfigIO and loaded the conf.
84 */
85 protected function initialize()
86 {
87 /*if (! file_exists(self::$CONFIG_FILE .'.php')) {
88 $this->configIO = new ConfigJson();
89 } else {
90 $this->configIO = new ConfigPhp();
91 }*/
92 $this->configIO = new ConfigPhp();
93 $this->load();
94 }
95
96 /**
97 * Load configuration in the ConfigurationManager.
98 */
99 protected function load()
100 {
101 $this->loadedConfig = $this->configIO->read($this->getConfigFile());
102 $this->setDefaultValues();
103 }
104
105 /**
106 * Get a setting.
107 *
108 * Supports nested settings with dot separated keys.
109 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
110 * or in JSON:
111 * { "config": { "stuff": {"option": "mysetting" } } } }
112 *
113 * @param string $setting Asked setting, keys separated with dots.
114 * @param string $default Default value if not found.
115 *
116 * @return mixed Found setting, or the default value.
117 */
118 public function get($setting, $default = '')
119 {
120 $settings = explode('.', $setting);
121 $value = self::getConfig($settings, $this->loadedConfig);
122 if ($value === self::$NOT_FOUND) {
123 return $default;
124 }
125 return $value;
126 }
127
128 /**
129 * Set a setting, and eventually write it.
130 *
131 * Supports nested settings with dot separated keys.
132 *
133 * @param string $setting Asked setting, keys separated with dots.
134 * @param string $value Value to set.
135 * @param bool $write Write the new setting in the config file, default false.
136 * @param bool $isLoggedIn User login state, default false.
137 *
138 * @throws Exception Invalid
139 */
140 public function set($setting, $value, $write = false, $isLoggedIn = false)
141 {
142 if (empty($setting) || ! is_string($setting)) {
143 throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
144 }
145
146 $settings = explode('.', $setting);
147 self::setConfig($settings, $value, $this->loadedConfig);
148 if ($write) {
149 $this->write($isLoggedIn);
150 }
151 }
152
153 /**
154 * Check if a settings exists.
155 *
156 * Supports nested settings with dot separated keys.
157 *
158 * @param string $setting Asked setting, keys separated with dots.
159 *
160 * @return bool true if the setting exists, false otherwise.
161 */
162 public function exists($setting)
163 {
164 $settings = explode('.', $setting);
165 $value = self::getConfig($settings, $this->loadedConfig);
166 if ($value === self::$NOT_FOUND) {
167 return false;
168 }
169 return true;
170 }
171
172 /**
173 * Call the config writer.
174 *
175 * @param bool $isLoggedIn User login state.
176 *
177 * @return bool True if the configuration has been successfully written, false otherwise.
178 *
179 * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
180 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
181 * @throws IOException: an error occurred while writing the new config file.
182 */
183 public function write($isLoggedIn)
184 {
185 // These fields are required in configuration.
186 $mandatoryFields = array(
187 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
188 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
189 );
190
191 // Only logged in user can alter config.
192 if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) {
193 throw new UnauthorizedConfigException();
194 }
195
196 // Check that all mandatory fields are provided in $conf.
197 foreach ($mandatoryFields as $field) {
198 if (! $this->exists($field)) {
199 throw new MissingFieldConfigException($field);
200 }
201 }
202
203 return $this->configIO->write($this->getConfigFile(), $this->loadedConfig);
204 }
205
206 /**
207 * Get the configuration file path.
208 *
209 * @return string Config file path.
210 */
211 public function getConfigFile()
212 {
213 return self::$CONFIG_FILE . $this->configIO->getExtension();
214 }
215
216 /**
217 * Recursive function which find asked setting in the loaded config.
218 *
219 * @param array $settings Ordered array which contains keys to find.
220 * @param array $conf Loaded settings, then sub-array.
221 *
222 * @return mixed Found setting or NOT_FOUND flag.
223 */
224 protected static function getConfig($settings, $conf)
225 {
226 if (!is_array($settings) || count($settings) == 0) {
227 return self::$NOT_FOUND;
228 }
229
230 $setting = array_shift($settings);
231 if (!isset($conf[$setting])) {
232 return self::$NOT_FOUND;
233 }
234
235 if (count($settings) > 0) {
236 return self::getConfig($settings, $conf[$setting]);
237 }
238 return $conf[$setting];
239 }
240
241 /**
242 * Recursive function which find asked setting in the loaded config.
243 *
244 * @param array $settings Ordered array which contains keys to find.
245 * @param mixed $value
246 * @param array $conf Loaded settings, then sub-array.
247 *
248 * @return mixed Found setting or NOT_FOUND flag.
249 */
250 protected static function setConfig($settings, $value, &$conf)
251 {
252 if (!is_array($settings) || count($settings) == 0) {
253 return self::$NOT_FOUND;
254 }
255
256 $setting = array_shift($settings);
257 if (count($settings) > 0) {
258 return self::setConfig($settings, $value, $conf[$setting]);
259 }
260 $conf[$setting] = $value;
261 }
262
263 /**
264 * Set a bunch of default values allowing Shaarli to start without a config file.
265 */
266 protected function setDefaultValues()
267 {
268 // Data subdirectory
269 $this->setEmpty('config.DATADIR', 'data');
270
271 // Main configuration file
272 $this->setEmpty('config.CONFIG_FILE', 'data/config.php');
273
274 // Link datastore
275 $this->setEmpty('config.DATASTORE', 'data/datastore.php');
276
277 // Banned IPs
278 $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php');
279
280 // Processed updates file.
281 $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt');
282
283 // Access log
284 $this->setEmpty('config.LOG_FILE', 'data/log.txt');
285
286 // For updates check of Shaarli
287 $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt');
288
289 // Set ENABLE_UPDATECHECK to disabled by default.
290 $this->setEmpty('config.ENABLE_UPDATECHECK', false);
291
292 // RainTPL cache directory (keep the trailing slash!)
293 $this->setEmpty('config.RAINTPL_TMP', 'tmp/');
294 // Raintpl template directory (keep the trailing slash!)
295 $this->setEmpty('config.RAINTPL_TPL', 'tpl/');
296
297 // Thumbnail cache directory
298 $this->setEmpty('config.CACHEDIR', 'cache');
299
300 // Atom & RSS feed cache directory
301 $this->setEmpty('config.PAGECACHE', 'pagecache');
302
303 // Ban IP after this many failures
304 $this->setEmpty('config.BAN_AFTER', 4);
305 // Ban duration for IP address after login failures (in seconds)
306 $this->setEmpty('config.BAN_DURATION', 1800);
307
308 // Feed options
309 // Enable RSS permalinks by default.
310 // This corresponds to the default behavior of shaarli before this was added as an option.
311 $this->setEmpty('config.ENABLE_RSS_PERMALINKS', true);
312 // If true, an extra "ATOM feed" button will be displayed in the toolbar
313 $this->setEmpty('config.SHOW_ATOM', false);
314
315 // Link display options
316 $this->setEmpty('config.HIDE_PUBLIC_LINKS', false);
317 $this->setEmpty('config.HIDE_TIMESTAMPS', false);
318 $this->setEmpty('config.LINKS_PER_PAGE', 20);
319
320 // Open Shaarli (true): anyone can add/edit/delete links without having to login
321 $this->setEmpty('config.OPEN_SHAARLI', false);
322
323 // Thumbnails
324 // Display thumbnails in links
325 $this->setEmpty('config.ENABLE_THUMBNAILS', true);
326 // Store thumbnails in a local cache
327 $this->setEmpty('config.ENABLE_LOCALCACHE', true);
328
329 // Update check frequency for Shaarli. 86400 seconds=24 hours
330 $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable');
331 $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400);
332
333 $this->setEmpty('redirector', '');
334 $this->setEmpty('config.REDIRECTOR_URLENCODE', true);
335
336 // Enabled plugins.
337 $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode'));
338
339 // Initialize plugin parameters array.
340 $this->setEmpty('plugins', array());
341 }
342
343 /**
344 * Set only if the setting does not exists.
345 *
346 * @param string $key Setting key.
347 * @param mixed $value Setting value.
348 */
349 protected function setEmpty($key, $value)
350 {
351 if (! $this->exists($key)) {
352 $this->set($key, $value);
353 }
354 }
355
356 /**
357 * @return ConfigIO
358 */
359 public function getConfigIO()
360 {
361 return $this->configIO;
362 }
363
364 /**
365 * @param ConfigIO $configIO
366 */
367 public function setConfigIO($configIO)
368 {
369 $this->configIO = $configIO;
370 }
371 }
372
373 /**
374 * Exception used if a mandatory field is missing in given configuration.
375 */
376 class MissingFieldConfigException extends Exception
377 {
378 public $field;
379
380 /**
381 * Construct exception.
382 *
383 * @param string $field field name missing.
384 */
385 public function __construct($field)
386 {
387 $this->field = $field;
388 $this->message = 'Configuration value is required for '. $this->field;
389 }
390 }
391
392 /**
393 * Exception used if an unauthorized attempt to edit configuration has been made.
394 */
395 class UnauthorizedConfigException extends Exception
396 {
397 /**
398 * Construct exception.
399 */
400 public function __construct()
401 {
402 $this->message = 'You are not authorized to alter config.';
403 }
404 }