1 | <?php |
||||
2 | /** |
||||
3 | * Config.php |
||||
4 | * |
||||
5 | * Config convenience class to access and set config variables. |
||||
6 | * |
||||
7 | * This program is free software: you can redistribute it and/or modify |
||||
8 | * it under the terms of the GNU General Public License as published by |
||||
9 | * the Free Software Foundation, either version 3 of the License, or |
||||
10 | * (at your option) any later version. |
||||
11 | * |
||||
12 | * This program is distributed in the hope that it will be useful, |
||||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
||||
15 | * GNU General Public License for more details. |
||||
16 | * |
||||
17 | * You should have received a copy of the GNU General Public License |
||||
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
19 | * |
||||
20 | * @package LibreNMS |
||||
21 | * @link http://librenms.org |
||||
22 | * @copyright 2017 Tony Murray |
||||
23 | * @author Tony Murray <[email protected]> |
||||
24 | */ |
||||
25 | |||||
26 | namespace LibreNMS; |
||||
27 | |||||
28 | use App\Models\GraphType; |
||||
29 | use Illuminate\Database\QueryException; |
||||
30 | use Illuminate\Support\Arr; |
||||
31 | use LibreNMS\DB\Eloquent; |
||||
32 | |||||
33 | class Config |
||||
34 | { |
||||
35 | /** |
||||
36 | * Load the config, if the database connected, pull in database settings. |
||||
37 | * |
||||
38 | * return &array |
||||
39 | */ |
||||
40 | public static function &load() |
||||
41 | { |
||||
42 | global $config; |
||||
43 | |||||
44 | self::loadFiles(); |
||||
45 | |||||
46 | // Make sure the database is connected |
||||
47 | if (Eloquent::isConnected() || (function_exists('dbIsConnected') && dbIsConnected())) { |
||||
48 | // pull in the database config settings |
||||
49 | self::mergeDb(); |
||||
50 | |||||
51 | // load graph types from the database |
||||
52 | self::loadGraphsFromDb(); |
||||
53 | |||||
54 | // process $config to tidy up |
||||
55 | self::processConfig(true); |
||||
56 | } else { |
||||
57 | // just process $config |
||||
58 | self::processConfig(false); |
||||
59 | } |
||||
60 | |||||
61 | return $config; |
||||
62 | } |
||||
63 | |||||
64 | /** |
||||
65 | * Load the user config from config.php, defaults.inc.php and definitions.inc.php, etc. |
||||
66 | * Erases existing config. |
||||
67 | * |
||||
68 | * @return array |
||||
69 | */ |
||||
70 | private static function &loadFiles() |
||||
71 | { |
||||
72 | global $config; |
||||
73 | |||||
74 | $config = []; // start fresh |
||||
75 | |||||
76 | $install_dir = realpath(__DIR__ . '/../'); |
||||
77 | $config['install_dir'] = $install_dir; |
||||
78 | |||||
79 | // load defaults |
||||
80 | require $install_dir . '/includes/defaults.inc.php'; |
||||
81 | require $install_dir . '/includes/definitions.inc.php'; |
||||
82 | |||||
83 | // import standard settings |
||||
84 | $macros = json_decode(file_get_contents($install_dir . '/misc/macros.json'), true); |
||||
85 | self::set('alert.macros.rule', $macros); |
||||
86 | |||||
87 | // Load user config |
||||
88 | @include $install_dir . '/config.php'; |
||||
89 | |||||
90 | return $config; |
||||
91 | } |
||||
92 | |||||
93 | |||||
94 | /** |
||||
95 | * Get a config value, if non existent null (or default if set) will be returned |
||||
96 | * |
||||
97 | * @param string $key period separated config variable name |
||||
98 | * @param mixed $default optional value to return if the setting is not set |
||||
99 | * @return mixed |
||||
100 | */ |
||||
101 | public static function get($key, $default = null) |
||||
102 | { |
||||
103 | global $config; |
||||
104 | |||||
105 | if (isset($config[$key])) { |
||||
106 | return $config[$key]; |
||||
107 | } |
||||
108 | |||||
109 | if (!str_contains($key, '.')) { |
||||
110 | return $default; |
||||
111 | } |
||||
112 | |||||
113 | $keys = explode('.', $key); |
||||
114 | |||||
115 | $curr = &$config; |
||||
116 | foreach ($keys as $k) { |
||||
117 | // do not add keys that don't exist |
||||
118 | if (!isset($curr[$k])) { |
||||
119 | return $default; |
||||
120 | } |
||||
121 | $curr = &$curr[$k]; |
||||
122 | } |
||||
123 | |||||
124 | if (is_null($curr)) { |
||||
125 | return $default; |
||||
126 | } |
||||
127 | |||||
128 | return $curr; |
||||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * Unset a config setting |
||||
133 | * or multiple |
||||
134 | * |
||||
135 | * @param string|array $key |
||||
136 | */ |
||||
137 | public static function forget($key) |
||||
138 | { |
||||
139 | global $config; |
||||
140 | Arr::forget($config, $key); |
||||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * Get a setting from a device, if that is not set, |
||||
145 | * fall back to the global config setting prefixed by $global_prefix |
||||
146 | * The key must be the same for the global setting and the device setting. |
||||
147 | * |
||||
148 | * @param array $device Device array |
||||
149 | * @param string $key Name of setting to fetch |
||||
150 | * @param string $global_prefix specify where the global setting lives in the global config |
||||
151 | * @param mixed $default will be returned if the setting is not set on the device or globally |
||||
152 | * @return mixed |
||||
153 | */ |
||||
154 | public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null) |
||||
155 | { |
||||
156 | if (isset($device[$key])) { |
||||
157 | return $device[$key]; |
||||
158 | } |
||||
159 | |||||
160 | if (isset($global_prefix)) { |
||||
161 | $key = "$global_prefix.$key"; |
||||
162 | } |
||||
163 | |||||
164 | return self::get($key, $default); |
||||
165 | } |
||||
166 | |||||
167 | /** |
||||
168 | * Get a setting from the $config['os'] array using the os of the given device |
||||
169 | * If that is not set, fallback to the same global config key |
||||
170 | * |
||||
171 | * @param string $os The os name |
||||
172 | * @param string $key period separated config variable name |
||||
173 | * @param mixed $default optional value to return if the setting is not set |
||||
174 | * @return mixed |
||||
175 | */ |
||||
176 | public static function getOsSetting($os, $key, $default = null) |
||||
177 | { |
||||
178 | global $config; |
||||
179 | |||||
180 | if ($os) { |
||||
181 | if (isset($config['os'][$os][$key])) { |
||||
182 | return $config['os'][$os][$key]; |
||||
183 | } |
||||
184 | |||||
185 | if (!str_contains($key, '.')) { |
||||
186 | return self::get($key, $default); |
||||
187 | } |
||||
188 | |||||
189 | $os_key = "os.$os.$key"; |
||||
190 | if (self::has($os_key)) { |
||||
191 | return self::get($os_key); |
||||
192 | } |
||||
193 | } |
||||
194 | |||||
195 | return self::get($key, $default); |
||||
196 | } |
||||
197 | |||||
198 | /** |
||||
199 | * Get the merged array from the global and os settings for the specified key. |
||||
200 | * Removes any duplicates. |
||||
201 | * When the arrays have keys, os settings take precedence over global settings |
||||
202 | * |
||||
203 | * @param string $os The os name |
||||
204 | * @param string $key period separated config variable name |
||||
205 | * @param array $default optional array to return if the setting is not set |
||||
206 | * @return array |
||||
207 | */ |
||||
208 | public static function getCombined($os, $key, $default = array()) |
||||
209 | { |
||||
210 | global $config; |
||||
211 | |||||
212 | if (!self::has($key)) { |
||||
213 | return self::get("os.$os.$key", $default); |
||||
214 | } |
||||
215 | |||||
216 | if (!isset($config['os'][$os][$key])) { |
||||
217 | if (!str_contains($key, '.')) { |
||||
218 | return self::get($key, $default); |
||||
219 | } |
||||
220 | if (!self::has("os.$os.$key")) { |
||||
221 | return self::get($key, $default); |
||||
222 | } |
||||
223 | } |
||||
224 | |||||
225 | return array_unique(array_merge( |
||||
226 | (array)self::get($key, $default), |
||||
227 | (array)self::getOsSetting($os, $key, $default) |
||||
228 | )); |
||||
229 | } |
||||
230 | |||||
231 | /** |
||||
232 | * Set a variable in the global config |
||||
233 | * |
||||
234 | * @param mixed $key period separated config variable name |
||||
235 | * @param mixed $value |
||||
236 | * @param bool $persist set the setting in the database so it persists across runs |
||||
237 | * @param string $default default (only set when initially created) |
||||
238 | * @param string $descr webui description (only set when initially created) |
||||
239 | * @param string $group webui group (only set when initially created) |
||||
240 | * @param string $sub_group webui subgroup (only set when initially created) |
||||
241 | */ |
||||
242 | public static function set($key, $value, $persist = false, $default = null, $descr = null, $group = null, $sub_group = null) |
||||
243 | { |
||||
244 | global $config; |
||||
245 | |||||
246 | if ($persist) { |
||||
247 | if (Eloquent::isConnected()) { |
||||
248 | try { |
||||
249 | $config_array = collect([ |
||||
250 | 'config_name' => $key, |
||||
251 | 'config_value' => $value, |
||||
252 | 'config_default' => $default, |
||||
253 | 'config_descr' => $descr, |
||||
254 | 'config_group' => $group, |
||||
255 | 'config_sub_group' => $sub_group, |
||||
256 | ])->filter(function ($value) { |
||||
257 | return !is_null($value); |
||||
258 | })->toArray(); |
||||
259 | |||||
260 | \App\Models\Config::updateOrCreate(['config_name' => $key], $config_array); |
||||
261 | } catch (QueryException $e) { |
||||
262 | // possibly table config doesn't exist yet |
||||
263 | global $debug; |
||||
0 ignored issues
–
show
|
|||||
264 | if ($debug) { |
||||
265 | echo $e; |
||||
266 | } |
||||
267 | } |
||||
268 | } else { |
||||
269 | $res = dbUpdate(array('config_value' => $value), 'config', '`config_name`=?', array($key)); |
||||
270 | if (!$res && !dbFetchCell('SELECT 1 FROM `config` WHERE `config_name`=?', array($key))) { |
||||
0 ignored issues
–
show
The expression
$res of type false|integer is loosely compared to false ; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
Loading history...
|
|||||
271 | $insert = array( |
||||
272 | 'config_name' => $key, |
||||
273 | 'config_value' => $value, |
||||
274 | 'config_default' => $default, |
||||
275 | 'config_descr' => $descr, |
||||
276 | 'config_group' => $group, |
||||
277 | 'config_sub_group' => $sub_group, |
||||
278 | ); |
||||
279 | dbInsert($insert, 'config'); |
||||
280 | } |
||||
281 | } |
||||
282 | } |
||||
283 | |||||
284 | $keys = explode('.', $key); |
||||
285 | |||||
286 | $curr = &$config; |
||||
287 | foreach ($keys as $k) { |
||||
288 | $curr = &$curr[$k]; |
||||
289 | } |
||||
290 | |||||
291 | $curr = $value; |
||||
292 | } |
||||
293 | |||||
294 | /** |
||||
295 | * Check if a setting is set |
||||
296 | * |
||||
297 | * @param string $key period separated config variable name |
||||
298 | * @return bool |
||||
299 | */ |
||||
300 | public static function has($key) |
||||
301 | { |
||||
302 | global $config; |
||||
303 | |||||
304 | if (isset($config[$key])) { |
||||
305 | return true; |
||||
306 | } |
||||
307 | |||||
308 | if (!str_contains($key, '.')) { |
||||
309 | return false; |
||||
310 | } |
||||
311 | |||||
312 | $keys = explode('.', $key); |
||||
313 | $last = array_pop($keys); |
||||
314 | |||||
315 | $curr = &$config; |
||||
316 | foreach ($keys as $k) { |
||||
317 | // do not add keys that don't exist |
||||
318 | if (!isset($curr[$k])) { |
||||
319 | return false; |
||||
320 | } |
||||
321 | $curr = &$curr[$k]; |
||||
322 | } |
||||
323 | |||||
324 | return is_array($curr) && isset($curr[$last]); |
||||
325 | } |
||||
326 | |||||
327 | /** |
||||
328 | * Serialise the whole configuration to json for use in external processes. |
||||
329 | * |
||||
330 | * @return string |
||||
331 | */ |
||||
332 | public static function json_encode() |
||||
333 | { |
||||
334 | global $config; |
||||
335 | |||||
336 | return json_encode($config); |
||||
337 | } |
||||
338 | |||||
339 | /** |
||||
340 | * Get the full configuration array |
||||
341 | * @return array |
||||
342 | */ |
||||
343 | public static function getAll() |
||||
344 | { |
||||
345 | global $config; |
||||
346 | return $config; |
||||
347 | } |
||||
348 | |||||
349 | /** |
||||
350 | * merge the database config with the global config |
||||
351 | * Global config overrides db |
||||
352 | */ |
||||
353 | private static function mergeDb() |
||||
354 | { |
||||
355 | global $config; |
||||
356 | |||||
357 | $db_config = []; |
||||
358 | |||||
359 | if (Eloquent::isConnected()) { |
||||
360 | try { |
||||
361 | \App\Models\Config::get(['config_name', 'config_value']) |
||||
362 | ->each(function ($item) use (&$db_config) { |
||||
363 | array_set($db_config, $item->config_name, $item->config_value); |
||||
0 ignored issues
–
show
The function
array_set() has been deprecated: Arr::set() should be used directly instead. Will be removed in Laravel 6.0.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.
Loading history...
|
|||||
364 | }); |
||||
365 | } catch (QueryException $e) { |
||||
366 | // possibly table config doesn't exist yet |
||||
367 | } |
||||
368 | |||||
369 | } else { |
||||
370 | foreach (dbFetchRows('SELECT `config_name`,`config_value` FROM `config`') as $obj) { |
||||
371 | self::assignArrayByPath($db_config, $obj['config_name'], $obj['config_value']); |
||||
372 | } |
||||
373 | } |
||||
374 | |||||
375 | $config = array_replace_recursive($db_config, $config); |
||||
376 | } |
||||
377 | |||||
378 | /** |
||||
379 | * Assign a value into the passed array by a path |
||||
380 | * 'snmp.version' = 'v1' becomes $arr['snmp']['version'] = 'v1' |
||||
381 | * |
||||
382 | * @param array $arr the array to insert the value into, will be modified in place |
||||
383 | * @param string $path the path to insert the value at |
||||
384 | * @param mixed $value the value to insert, will be type cast |
||||
385 | * @param string $separator path separator |
||||
386 | */ |
||||
387 | private static function assignArrayByPath(&$arr, $path, $value, $separator = '.') |
||||
388 | { |
||||
389 | // type cast value. Is this needed here? |
||||
390 | if (filter_var($value, FILTER_VALIDATE_INT)) { |
||||
391 | $value = (int)$value; |
||||
392 | } elseif (filter_var($value, FILTER_VALIDATE_FLOAT)) { |
||||
393 | $value = (float)$value; |
||||
394 | } elseif (filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null) { |
||||
395 | $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); |
||||
396 | } |
||||
397 | |||||
398 | $keys = explode($separator, $path); |
||||
399 | |||||
400 | // walk the array creating keys if they don't exist |
||||
401 | foreach ($keys as $key) { |
||||
402 | $arr = &$arr[$key]; |
||||
403 | } |
||||
404 | // assign the variable |
||||
405 | $arr = $value; |
||||
406 | } |
||||
407 | |||||
408 | private static function loadGraphsFromDb() |
||||
409 | { |
||||
410 | global $config; |
||||
0 ignored issues
–
show
Compatibility
Best Practice
introduced
by
Use of
global functionality is not recommended; it makes your code harder to test, and less reusable.
Instead of relying on 1. Pass all data via parametersfunction myFunction($a, $b) {
// Do something
}
2. Create a class that maintains your stateclass MyClass {
private $a;
private $b;
public function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
public function myFunction() {
// Do something
}
}
Loading history...
|
|||||
411 | |||||
412 | if (Eloquent::isConnected()) { |
||||
413 | try { |
||||
414 | $graph_types = GraphType::all()->toArray(); |
||||
415 | } catch (QueryException $e) { |
||||
416 | // possibly table config doesn't exist yet |
||||
417 | $graph_types = []; |
||||
418 | } |
||||
419 | } else { |
||||
420 | $graph_types = dbFetchRows('SELECT * FROM graph_types'); |
||||
421 | } |
||||
422 | |||||
423 | // load graph types from the database |
||||
424 | foreach ($graph_types as $graph) { |
||||
425 | $g = []; |
||||
426 | foreach ($graph as $k => $v) { |
||||
427 | if (strpos($k, 'graph_') == 0) { |
||||
428 | // remove leading 'graph_' from column name |
||||
429 | $key = str_replace('graph_', '', $k); |
||||
430 | } else { |
||||
431 | $key = $k; |
||||
432 | } |
||||
433 | $g[$key] = $v; |
||||
434 | } |
||||
435 | |||||
436 | $config['graph_types'][$g['type']][$g['subtype']] = $g; |
||||
437 | } |
||||
438 | } |
||||
439 | |||||
440 | /** |
||||
441 | * Proces the config after it has been loaded. |
||||
442 | * Make sure certain variables have been set properly and |
||||
443 | * |
||||
444 | * @param bool $persist Save binary locations and other settings to the database. |
||||
445 | */ |
||||
446 | private static function processConfig($persist = true) |
||||
447 | { |
||||
448 | if (!self::get('email_from')) { |
||||
449 | self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>'); |
||||
450 | } |
||||
451 | |||||
452 | // If we're on SSL, let's properly detect it |
||||
453 | if (isset($_SERVER['HTTPS'])) { |
||||
454 | self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url'))); |
||||
455 | } |
||||
456 | |||||
457 | // Define some variables if they aren't set by user definition in config.php |
||||
458 | self::setDefault('html_dir', '%s/html', ['install_dir']); |
||||
459 | self::setDefault('rrd_dir', '%s/rrd', ['install_dir']); |
||||
460 | self::setDefault('mib_dir', '%s/mibs', ['install_dir']); |
||||
461 | self::setDefault('log_dir', '%s/logs', ['install_dir']); |
||||
462 | self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']); |
||||
463 | self::setDefault('plugin_dir', '%s/plugins', ['html_dir']); |
||||
464 | self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp'); |
||||
465 | // self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']); // FIXME email_from set because alerting config |
||||
466 | |||||
467 | // deprecated variables |
||||
468 | self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile'); |
||||
469 | self::deprecatedVariable('fping_options.millisec', 'fping_options.interval'); |
||||
470 | self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf'); |
||||
471 | self::deprecatedVariable('oxidized.group', 'oxidized.maps.group'); |
||||
472 | |||||
473 | // make sure we have full path to binaries in case PATH isn't set |
||||
474 | foreach (array('fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute', 'traceroute6') as $bin) { |
||||
475 | if (!is_executable(self::get($bin))) { |
||||
476 | self::set($bin, self::locateBinary($bin), $persist, $bin, "Path to $bin", 'external', 'paths'); |
||||
477 | } |
||||
478 | } |
||||
479 | } |
||||
480 | |||||
481 | /** |
||||
482 | * Set default values for defaults that depend on other settings, if they are not already loaded |
||||
483 | * |
||||
484 | * @param string $key |
||||
485 | * @param string $value value to set to key or vsprintf() format string for values below |
||||
486 | * @param array $format_values array of keys to send to vsprintf() |
||||
487 | */ |
||||
488 | private static function setDefault($key, $value, $format_values = []) |
||||
489 | { |
||||
490 | if (!self::has($key)) { |
||||
491 | if (is_string($value)) { |
||||
492 | $format_values = array_map('self::get', $format_values); |
||||
493 | self::set($key, vsprintf($value, $format_values)); |
||||
494 | } else { |
||||
495 | self::set($key, $value); |
||||
496 | } |
||||
497 | } |
||||
498 | } |
||||
499 | |||||
500 | /** |
||||
501 | * Copy data from old variables to new ones. |
||||
502 | * |
||||
503 | * @param $old |
||||
504 | * @param $new |
||||
505 | */ |
||||
506 | private static function deprecatedVariable($old, $new) |
||||
507 | { |
||||
508 | if (self::has($old)) { |
||||
509 | global $debug; |
||||
510 | if ($debug) { |
||||
511 | echo "Copied deprecated config $old to $new\n"; |
||||
512 | } |
||||
513 | self::set($new, self::get($old)); |
||||
514 | } |
||||
515 | } |
||||
516 | |||||
517 | /** |
||||
518 | * Get just the database connection settings from config.php |
||||
519 | * |
||||
520 | * @return array (keys: db_host, db_port, db_name, db_user, db_pass, db_socket) |
||||
521 | */ |
||||
522 | public static function getDatabaseSettings() |
||||
523 | { |
||||
524 | // Do not access global $config in this function! |
||||
525 | |||||
526 | $keys = $config = [ |
||||
527 | 'db_host' => '', |
||||
528 | 'db_port' => '', |
||||
529 | 'db_name' => '', |
||||
530 | 'db_user' => '', |
||||
531 | 'db_pass' => '', |
||||
532 | 'db_socket' => '', |
||||
533 | ]; |
||||
534 | |||||
535 | if (is_file(__DIR__ . '/../config.php')) { |
||||
536 | include __DIR__ . '/../config.php'; |
||||
537 | } |
||||
538 | |||||
539 | // Check for testing database |
||||
540 | if (isset($config['test_db_name'])) { |
||||
541 | putenv('DB_TEST_DATABASE=' . $config['test_db_name']); |
||||
542 | } |
||||
543 | if (isset($config['test_db_user'])) { |
||||
544 | putenv('DB_TEST_USERNAME=' . $config['test_db_user']); |
||||
545 | } |
||||
546 | if (isset($config['test_db_pass'])) { |
||||
547 | putenv('DB_TEST_PASSWORD=' . $config['test_db_pass']); |
||||
548 | } |
||||
549 | |||||
550 | return array_intersect_key($config, $keys); // return only the db settings |
||||
551 | } |
||||
552 | |||||
553 | /** |
||||
554 | * Locate the actual path of a binary |
||||
555 | * |
||||
556 | * @param $binary |
||||
557 | * @return mixed |
||||
558 | */ |
||||
559 | public static function locateBinary($binary) |
||||
560 | { |
||||
561 | if (!str_contains($binary, '/')) { |
||||
562 | $output = `whereis -b $binary`; |
||||
563 | $list = trim(substr($output, strpos($output, ':') + 1)); |
||||
564 | $targets = explode(' ', $list); |
||||
565 | foreach ($targets as $target) { |
||||
566 | if (is_executable($target)) { |
||||
567 | return $target; |
||||
568 | } |
||||
569 | } |
||||
570 | } |
||||
571 | return $binary; |
||||
572 | } |
||||
573 | } |
||||
574 |
Instead of relying on
global
state, we recommend one of these alternatives:1. Pass all data via parameters
2. Create a class that maintains your state