]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/config/ConfigManager.php
Introduce a configuration manager (not plugged yet)
[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 * Rebuild the loaded config array from config files.
67 */
68 public function reload()
69 {
70 $this->initialize();
71 }
72
73 /**
74 * Initialize loaded conf in ConfigManager.
75 */
76 protected function initialize()
77 {
78 /*if (! file_exists(self::$CONFIG_FILE .'.php')) {
79 $this->configIO = new ConfigJson();
80 } else {
81 $this->configIO = new ConfigPhp();
82 }*/
83 $this->configIO = new ConfigPhp();
84 $this->loadedConfig = $this->configIO->read(self::$CONFIG_FILE);
85 $this->setDefaultValues();
86 }
87
88 /**
89 * Get a setting.
90 *
91 * Supports nested settings with dot separated keys.
92 * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
93 * or in JSON:
94 * { "config": { "stuff": {"option": "mysetting" } } } }
95 *
96 * @param string $setting Asked setting, keys separated with dots.
97 * @param string $default Default value if not found.
98 *
99 * @return mixed Found setting, or the default value.
100 */
101 public function get($setting, $default = '')
102 {
103 $settings = explode('.', $setting);
104 $value = self::getConfig($settings, $this->loadedConfig);
105 if ($value === self::$NOT_FOUND) {
106 return $default;
107 }
108 return $value;
109 }
110
111 /**
112 * Set a setting, and eventually write it.
113 *
114 * Supports nested settings with dot separated keys.
115 *
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.
120 */
121 public function set($setting, $value, $write = false, $isLoggedIn = false)
122 {
123 $settings = explode('.', $setting);
124 self::setConfig($settings, $value, $this->loadedConfig);
125 if ($write) {
126 $this->write($isLoggedIn);
127 }
128 }
129
130 /**
131 * Check if a settings exists.
132 *
133 * Supports nested settings with dot separated keys.
134 *
135 * @param string $setting Asked setting, keys separated with dots.
136 *
137 * @return bool true if the setting exists, false otherwise.
138 */
139 public function exists($setting)
140 {
141 $settings = explode('.', $setting);
142 $value = self::getConfig($settings, $this->loadedConfig);
143 if ($value === self::$NOT_FOUND) {
144 return false;
145 }
146 return true;
147 }
148
149 /**
150 * Call the config writer.
151 *
152 * @param bool $isLoggedIn User login state.
153 *
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.
157 */
158 public function write($isLoggedIn)
159 {
160 // These fields are required in configuration.
161 $mandatoryFields = array(
162 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
163 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
164 );
165
166 // Only logged in user can alter config.
167 if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) {
168 throw new UnauthorizedConfigException();
169 }
170
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);
175 }
176 }
177
178 $this->configIO->write(self::$CONFIG_FILE, $this->loadedConfig);
179 }
180
181 /**
182 * Get the configuration file path.
183 *
184 * @return string Config file path.
185 */
186 public function getConfigFile()
187 {
188 return self::$CONFIG_FILE . $this->configIO->getExtension();
189 }
190
191 /**
192 * Recursive function which find asked setting in the loaded config.
193 *
194 * @param array $settings Ordered array which contains keys to find.
195 * @param array $conf Loaded settings, then sub-array.
196 *
197 * @return mixed Found setting or NOT_FOUND flag.
198 */
199 protected static function getConfig($settings, $conf)
200 {
201 if (!is_array($settings) || count($settings) == 0) {
202 return self::$NOT_FOUND;
203 }
204
205 $setting = array_shift($settings);
206 if (!isset($conf[$setting])) {
207 return self::$NOT_FOUND;
208 }
209
210 if (count($settings) > 0) {
211 return self::getConfig($settings, $conf[$setting]);
212 }
213 return $conf[$setting];
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 mixed $value
221 * @param array $conf Loaded settings, then sub-array.
222 *
223 * @return mixed Found setting or NOT_FOUND flag.
224 */
225 protected static function setConfig($settings, $value, &$conf)
226 {
227 if (!is_array($settings) || count($settings) == 0) {
228 return self::$NOT_FOUND;
229 }
230
231 $setting = array_shift($settings);
232 if (count($settings) > 0) {
233 return self::setConfig($settings, $value, $conf[$setting]);
234 }
235 $conf[$setting] = $value;
236 }
237
238 /**
239 * Set a bunch of default values allowing Shaarli to start without a config file.
240 */
241 protected function setDefaultValues()
242 {
243 // Data subdirectory
244 $this->setEmpty('config.DATADIR', 'data');
245
246 // Main configuration file
247 $this->setEmpty('config.CONFIG_FILE', 'data/config.php');
248
249 // Link datastore
250 $this->setEmpty('config.DATASTORE', 'data/datastore.php');
251
252 // Banned IPs
253 $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php');
254
255 // Processed updates file.
256 $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt');
257
258 // Access log
259 $this->setEmpty('config.LOG_FILE', 'data/log.txt');
260
261 // For updates check of Shaarli
262 $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt');
263
264 // Set ENABLE_UPDATECHECK to disabled by default.
265 $this->setEmpty('config.ENABLE_UPDATECHECK', false);
266
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/');
271
272 // Thumbnail cache directory
273 $this->setEmpty('config.CACHEDIR', 'cache');
274
275 // Atom & RSS feed cache directory
276 $this->setEmpty('config.PAGECACHE', 'pagecache');
277
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);
282
283 // Feed options
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);
289
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);
294
295 // Open Shaarli (true): anyone can add/edit/delete links without having to login
296 $this->setEmpty('config.OPEN_SHAARLI', false);
297
298 // Thumbnails
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);
303
304 // Update check frequency for Shaarli. 86400 seconds=24 hours
305 $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable');
306 $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400);
307
308 $this->setEmpty('redirector', '');
309 $this->setEmpty('config.REDIRECTOR_URLENCODE', true);
310
311 // Enabled plugins.
312 $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode'));
313
314 // Initialize plugin parameters array.
315 $this->setEmpty('plugins', array());
316 }
317
318 /**
319 * Set only if the setting does not exists.
320 *
321 * @param string $key Setting key.
322 * @param mixed $value Setting value.
323 */
324 protected function setEmpty($key, $value)
325 {
326 if (! $this->exists($key)) {
327 $this->set($key, $value);
328 }
329 }
330 }
331
332 /**
333 * Exception used if a mandatory field is missing in given configuration.
334 */
335 class MissingFieldConfigException extends Exception
336 {
337 public $field;
338
339 /**
340 * Construct exception.
341 *
342 * @param string $field field name missing.
343 */
344 public function __construct($field)
345 {
346 $this->field = $field;
347 $this->message = 'Configuration value is required for '. $this->field;
348 }
349 }
350
351 /**
352 * Exception used if an unauthorized attempt to edit configuration has been made.
353 */
354 class UnauthorizedConfigException extends Exception
355 {
356 /**
357 * Construct exception.
358 */
359 public function __construct()
360 {
361 $this->message = 'You are not authorized to alter config.';
362 }
363 }