diff options
author | ArthurHoaro <arthur@hoa.ro> | 2016-05-18 21:43:59 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2016-06-11 09:30:56 +0200 |
commit | 59404d7909b21682ec0782778452a8a70e38b25e (patch) | |
tree | 52bbe0b390ecd37f80128d1f8b4f80c3834734a8 /application/config/ConfigManager.php | |
parent | 823a363c3b2e10008a607c8b69c1a3d4e9b44ea1 (diff) | |
download | Shaarli-59404d7909b21682ec0782778452a8a70e38b25e.tar.gz Shaarli-59404d7909b21682ec0782778452a8a70e38b25e.tar.zst Shaarli-59404d7909b21682ec0782778452a8a70e38b25e.zip |
Introduce a configuration manager (not plugged yet)
Diffstat (limited to 'application/config/ConfigManager.php')
-rw-r--r-- | application/config/ConfigManager.php | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php new file mode 100644 index 00000000..dfe9eeb9 --- /dev/null +++ b/application/config/ConfigManager.php | |||
@@ -0,0 +1,363 @@ | |||
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 | } | ||