Passed
Push — master ( 4cf584...63442e )
by Tony
10:02
created

LibreNMS/Config.php (1 issue)

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
    private static $config;
36
37
    /**
38
     * Load the config, if the database connected, pull in database settings.
39
     *
40
     * return &array
41
     */
42
    public static function load()
43
    {
44
        if (!is_null(self::$config)) {
45
            return self::$config;
46
        }
47
48
        self::loadFiles();
49
50
        // Make sure the database is connected
51
        if (Eloquent::isConnected()) {
52
            // pull in the database config settings
53
            self::mergeDb();
54
55
            // load graph types from the database
56
            self::loadGraphsFromDb();
57
58
            // process $config to tidy up
59
            self::processConfig(true);
60
        } else {
61
            // just process $config
62
            self::processConfig(false);
63
        }
64
65
        // set to global for legacy/external things
66
        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 global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
67
        $config = self::$config;
68
69
        return self::$config;
70
    }
71
72
    /**
73
     * Reload the config from files/db
74
     * @return mixed
75
     */
76
    public static function reload()
77
    {
78
        self::$config = null;
79
        return self::load();
80
    }
81
82
    /**
83
     * Load the user config from config.php, defaults.inc.php and definitions.inc.php, etc.
84
     * Erases existing config.
85
     *
86
     * @return array
87
     */
88
    private static function &loadFiles()
89
    {
90
        $config = []; // start fresh
91
92
        $install_dir = realpath(__DIR__ . '/../');
93
        $config['install_dir'] = $install_dir;
94
95
        // load defaults
96
        require $install_dir . '/includes/defaults.inc.php';
97
        require $install_dir . '/includes/definitions.inc.php';
98
99
        // import standard settings
100
        $macros = json_decode(file_get_contents($install_dir . '/misc/macros.json'), true);
101
        $config['alert']['macros']['rule'] = $macros;
102
103
        // Load user config
104
        @include $install_dir . '/config.php';
105
106
        // set it
107
        self::$config = $config;
108
109
        return self::$config;
110
    }
111
112
113
    /**
114
     * Get a config value, if non existent null (or default if set) will be returned
115
     *
116
     * @param string $key period separated config variable name
117
     * @param mixed $default optional value to return if the setting is not set
118
     * @return mixed
119
     */
120
    public static function get($key, $default = null)
121
    {
122
        if (isset(self::$config[$key])) {
123
            return self::$config[$key];
124
        }
125
126
        if (!str_contains($key, '.')) {
127
            return $default;
128
        }
129
130
        $keys = explode('.', $key);
131
132
        $curr = &self::$config;
133
        foreach ($keys as $k) {
134
            // do not add keys that don't exist
135
            if (!isset($curr[$k])) {
136
                return $default;
137
            }
138
            $curr = &$curr[$k];
139
        }
140
141
        if (is_null($curr)) {
142
            return $default;
143
        }
144
145
        return $curr;
146
    }
147
148
    /**
149
     * Unset a config setting
150
     * or multiple
151
     *
152
     * @param string|array $key
153
     */
154
    public static function forget($key)
155
    {
156
        Arr::forget(self::$config, $key);
157
    }
158
159
    /**
160
     * Get a setting from a device, if that is not set,
161
     * fall back to the global config setting prefixed by $global_prefix
162
     * The key must be the same for the global setting and the device setting.
163
     *
164
     * @param array $device Device array
165
     * @param string $key Name of setting to fetch
166
     * @param string $global_prefix specify where the global setting lives in the global config
167
     * @param mixed $default will be returned if the setting is not set on the device or globally
168
     * @return mixed
169
     */
170
    public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null)
171
    {
172
        if (isset($device[$key])) {
173
            return $device[$key];
174
        }
175
176
        if (isset($global_prefix)) {
177
            $key = "$global_prefix.$key";
178
        }
179
180
        return self::get($key, $default);
181
    }
182
183
    /**
184
     * Get a setting from the $config['os'] array using the os of the given device
185
     * If that is not set, fallback to the same global config key
186
     *
187
     * @param string $os The os name
188
     * @param string $key period separated config variable name
189
     * @param mixed $default optional value to return if the setting is not set
190
     * @return mixed
191
     */
192
    public static function getOsSetting($os, $key, $default = null)
193
    {
194
        if ($os) {
195
            if (isset(self::$config['os'][$os][$key])) {
196
                return self::$config['os'][$os][$key];
197
            }
198
199
            if (!str_contains($key, '.')) {
200
                return self::get($key, $default);
201
            }
202
203
            $os_key = "os.$os.$key";
204
            if (self::has($os_key)) {
205
                return self::get($os_key);
206
            }
207
        }
208
209
        return self::get($key, $default);
210
    }
211
212
    /**
213
     * Get the merged array from the global and os settings for the specified key.
214
     * Removes any duplicates.
215
     * When the arrays have keys, os settings take precedence over global settings
216
     *
217
     * @param string $os The os name
218
     * @param string $key period separated config variable name
219
     * @param array $default optional array to return if the setting is not set
220
     * @return array
221
     */
222
    public static function getCombined($os, $key, $default = array())
223
    {
224
        if (!self::has($key)) {
225
            return self::get("os.$os.$key", $default);
226
        }
227
228
        if (!isset(self::$config['os'][$os][$key])) {
229
            if (!str_contains($key, '.')) {
230
                return self::get($key, $default);
231
            }
232
            if (!self::has("os.$os.$key")) {
233
                return self::get($key, $default);
234
            }
235
        }
236
237
        return array_unique(array_merge(
238
            (array)self::get($key, $default),
239
            (array)self::getOsSetting($os, $key, $default)
240
        ));
241
    }
242
243
    /**
244
     * Set a variable in the global config
245
     *
246
     * @param mixed $key period separated config variable name
247
     * @param mixed $value
248
     * @param bool $persist set the setting in the database so it persists across runs
249
     * @param string $default default (only set when initially created)
250
     * @param string $descr webui description (only set when initially created)
251
     * @param string $group webui group (only set when initially created)
252
     * @param string $sub_group webui subgroup (only set when initially created)
253
     */
254
    public static function set($key, $value, $persist = false, $default = null, $descr = null, $group = null, $sub_group = null)
255
    {
256
        if ($persist) {
257
            try {
258
                \App\Models\Config::updateOrCreate(['config_name' => $key], collect([
259
                    'config_name' => $key,
260
                    'config_default' => $default,
261
                    'config_descr' => $descr,
262
                    'config_group' => $group,
263
                    'config_sub_group' => $sub_group,
264
                ])->filter(function ($value, $field) {
265
                    return !is_null($value);
266
                })->put('config_value', $value)->toArray());
267
            } catch (QueryException $e) {
268
                if (class_exists(\Log::class)) {
269
                    \Log::error($e);
270
                }
271
                global $debug;
272
                if ($debug) {
273
                    echo $e;
274
                }
275
            }
276
        }
277
278
        $keys = explode('.', $key);
279
280
        $curr = &self::$config;
281
        foreach ($keys as $k) {
282
            $curr = &$curr[$k];
283
        }
284
285
        $curr = $value;
286
    }
287
288
    /**
289
     * Check if a setting is set
290
     *
291
     * @param string $key period separated config variable name
292
     * @return bool
293
     */
294
    public static function has($key)
295
    {
296
        if (isset(self::$config[$key])) {
297
            return true;
298
        }
299
300
        if (!str_contains($key, '.')) {
301
            return false;
302
        }
303
304
        $keys = explode('.', $key);
305
        $last = array_pop($keys);
306
307
        $curr = &self::$config;
308
        foreach ($keys as $k) {
309
            // do not add keys that don't exist
310
            if (!isset($curr[$k])) {
311
                return false;
312
            }
313
            $curr = &$curr[$k];
314
        }
315
316
        return is_array($curr) && isset($curr[$last]);
317
    }
318
319
    /**
320
     * Serialise the whole configuration to json for use in external processes.
321
     *
322
     * @return string
323
     */
324
    public static function json_encode()
325
    {
326
        return json_encode(self::$config);
327
    }
328
329
    /**
330
     * Get the full configuration array
331
     * @return array
332
     */
333
    public static function getAll()
334
    {
335
        return self::$config;
336
    }
337
338
    /**
339
     * merge the database config with the global config
340
     * Global config overrides db
341
     */
342
    private static function mergeDb()
343
    {
344
        $db_config = [];
345
346
        try {
347
            \App\Models\Config::get(['config_name', 'config_value'])
348
                ->each(function ($item) use (&$db_config) {
349
                    Arr::set($db_config, $item->config_name, $item->config_value);
350
                });
351
        } catch (QueryException $e) {
352
            // possibly table config doesn't exist yet
353
        }
354
355
        self::$config = array_replace_recursive($db_config, self::$config);
356
    }
357
358
    private static function loadGraphsFromDb()
359
    {
360
        try {
361
            $graph_types = GraphType::all()->toArray();
362
        } catch (QueryException $e) {
363
            // possibly table config doesn't exist yet
364
            $graph_types = [];
365
        }
366
367
        // load graph types from the database
368
        foreach ($graph_types as $graph) {
369
            $g = [];
370
            foreach ($graph as $k => $v) {
371
                if (strpos($k, 'graph_') == 0) {
372
                    // remove leading 'graph_' from column name
373
                    $key = str_replace('graph_', '', $k);
374
                } else {
375
                    $key = $k;
376
                }
377
                $g[$key] = $v;
378
            }
379
380
            self::$config['graph_types'][$g['type']][$g['subtype']] = $g;
381
        }
382
    }
383
384
    /**
385
     * Proces the config after it has been loaded.
386
     * Make sure certain variables have been set properly and
387
     *
388
     * @param bool $persist Save binary locations and other settings to the database.
389
     */
390
    private static function processConfig($persist = true)
391
    {
392
        if (!self::get('email_from')) {
393
            self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>');
394
        }
395
396
        // If we're on SSL, let's properly detect it
397
        if (isset($_SERVER['HTTPS'])) {
398
            self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url')));
399
        }
400
401
        // Define some variables if they aren't set by user definition in config.php
402
        self::setDefault('html_dir', '%s/html', ['install_dir']);
403
        self::setDefault('rrd_dir', '%s/rrd', ['install_dir']);
404
        self::setDefault('mib_dir', '%s/mibs', ['install_dir']);
405
        self::setDefault('log_dir', '%s/logs', ['install_dir']);
406
        self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']);
407
        self::setDefault('plugin_dir', '%s/plugins', ['html_dir']);
408
        self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp');
409
//        self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']);  // FIXME email_from set because alerting config
410
411
        // deprecated variables
412
        self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile');
413
        self::deprecatedVariable('fping_options.millisec', 'fping_options.interval');
414
        self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf');
415
        self::deprecatedVariable('oxidized.group', 'oxidized.maps.group');
416
417
        // make sure we have full path to binaries in case PATH isn't set
418
        foreach (array('fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute', 'traceroute6') as $bin) {
419
            if (!is_executable(self::get($bin))) {
420
                self::set($bin, self::locateBinary($bin), $persist, $bin, "Path to $bin", 'external', 'paths');
421
            }
422
        }
423
    }
424
425
    /**
426
     * Set default values for defaults that depend on other settings, if they are not already loaded
427
     *
428
     * @param string $key
429
     * @param string $value value to set to key or vsprintf() format string for values below
430
     * @param array $format_values array of keys to send to vsprintf()
431
     */
432
    private static function setDefault($key, $value, $format_values = [])
433
    {
434
        if (!self::has($key)) {
435
            if (is_string($value)) {
436
                $format_values = array_map('self::get', $format_values);
437
                self::set($key, vsprintf($value, $format_values));
438
            } else {
439
                self::set($key, $value);
440
            }
441
        }
442
    }
443
444
    /**
445
     * Copy data from old variables to new ones.
446
     *
447
     * @param $old
448
     * @param $new
449
     */
450
    private static function deprecatedVariable($old, $new)
451
    {
452
        if (self::has($old)) {
453
            global $debug;
454
            if ($debug) {
455
                echo "Copied deprecated config $old to $new\n";
456
            }
457
            self::set($new, self::get($old));
458
        }
459
    }
460
461
    /**
462
     * Get just the database connection settings from config.php
463
     *
464
     * @return array (keys: db_host, db_port, db_name, db_user, db_pass, db_socket)
465
     */
466
    public static function getDatabaseSettings()
467
    {
468
        // Do not access global $config in this function!
469
470
        $keys = $config = [
471
            'db_host' => '',
472
            'db_port' => '',
473
            'db_name' => '',
474
            'db_user' => '',
475
            'db_pass' => '',
476
            'db_socket' => '',
477
        ];
478
479
        if (is_file(__DIR__ . '/../config.php')) {
480
            include __DIR__ . '/../config.php';
481
        }
482
483
        // Check for testing database
484
        if (isset($config['test_db_name'])) {
485
            putenv('DB_TEST_DATABASE=' . $config['test_db_name']);
486
        }
487
        if (isset($config['test_db_user'])) {
488
            putenv('DB_TEST_USERNAME=' . $config['test_db_user']);
489
        }
490
        if (isset($config['test_db_pass'])) {
491
            putenv('DB_TEST_PASSWORD=' . $config['test_db_pass']);
492
        }
493
494
        return array_intersect_key($config, $keys); // return only the db settings
495
    }
496
497
    /**
498
     * Locate the actual path of a binary
499
     *
500
     * @param $binary
501
     * @return mixed
502
     */
503
    public static function locateBinary($binary)
504
    {
505
        if (!str_contains($binary, '/')) {
506
            $output = `whereis -b $binary`;
507
            $list = trim(substr($output, strpos($output, ':') + 1));
508
            $targets = explode(' ', $list);
509
            foreach ($targets as $target) {
510
                if (is_executable($target)) {
511
                    return $target;
512
                }
513
            }
514
        }
515
        return $binary;
516
    }
517
}
518