Config   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 517
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 70
eloc 187
c 1
b 0
f 0
dl 0
loc 517
rs 2.8

25 Methods

Rating   Name   Duplication   Size   Complexity  
A toJson() 0 3 1
A has() 0 11 3
A set() 0 3 1
A getCombined() 0 18 5
A getDeviceSetting() 0 11 3
A forget() 0 3 1
A get() 0 11 3
A loadUserConfigFile() 0 4 1
A processDefaults() 0 19 5
A loadGraphsFromDb() 0 23 5
A getAll() 0 3 1
A loadDB() 0 17 3
A getDefinitions() 0 3 1
A loadDefaults() 0 16 3
A erase() 0 7 2
A load() 0 20 2
A persist() 0 22 4
B processConfig() 0 56 9
A reload() 0 5 1
A getOsSetting() 0 16 4
A deprecatedVariable() 0 7 3
A locateBinary() 0 14 4
A setDefault() 0 8 3
A populateLegacyDbCredentials() 0 10 1
A populateTime() 0 19 1

How to fix   Complexity   

Complex Class

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

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 <https://www.gnu.org/licenses/>.
19
 *
20
 * @link       https://www.librenms.org
21
 *
22
 * @copyright  2017 Tony Murray
23
 * @author     Tony Murray <[email protected]>
24
 */
25
26
namespace LibreNMS;
27
28
use App\Models\GraphType;
29
use Exception;
30
use Illuminate\Database\QueryException;
31
use Illuminate\Support\Arr;
32
use Illuminate\Support\Str;
33
use LibreNMS\DB\Eloquent;
34
use LibreNMS\Util\Debug;
35
use Log;
36
37
class Config
38
{
39
    private static $config;
40
41
    /**
42
     * Load the config, if the database connected, pull in database settings.
43
     *
44
     * return &array
45
     */
46
    public static function load()
47
    {
48
        // don't reload the config if it is already loaded, reload() should be used for that
49
        if (! is_null(self::$config)) {
50
            return self::$config;
51
        }
52
53
        // merge all config sources together config_definitions.json > db config > config.php
54
        self::loadDefaults();
55
        self::loadDB();
56
        self::loadUserConfigFile(self::$config);
57
58
        // final cleanups and validations
59
        self::processConfig();
60
61
        // set to global for legacy/external things (is this needed?)
62
        global $config;
63
        $config = self::$config;
64
65
        return self::$config;
66
    }
67
68
    /**
69
     * Reload the config from files/db
70
     *
71
     * @return mixed
72
     */
73
    public static function reload()
74
    {
75
        self::$config = null;
76
77
        return self::load();
78
    }
79
80
    /**
81
     * Get the config setting definitions
82
     *
83
     * @return array
84
     */
85
    public static function getDefinitions()
86
    {
87
        return json_decode(file_get_contents(base_path('misc/config_definitions.json')), true)['config'];
88
    }
89
90
    private static function loadDefaults()
91
    {
92
        self::$config['install_dir'] = base_path();
93
        $definitions = self::getDefinitions();
94
95
        foreach ($definitions as $path => $def) {
96
            if (array_key_exists('default', $def)) {
97
                Arr::set(self::$config, $path, $def['default']);
98
            }
99
        }
100
101
        // load macros from json
102
        $macros = json_decode(file_get_contents(base_path('misc/macros.json')), true);
103
        Arr::set(self::$config, 'alert.macros.rule', $macros);
104
105
        self::processDefaults();
106
    }
107
108
    /**
109
     * Load the user config from config.php
110
     *
111
     * @param  array  $config  (this should be self::$config)
112
     */
113
    private static function loadUserConfigFile(&$config)
114
    {
115
        // Load user config file
116
        @include base_path('config.php');
117
    }
118
119
    /**
120
     * Get a config value, if non existent null (or default if set) will be returned
121
     *
122
     * @param  string  $key  period separated config variable name
123
     * @param  mixed  $default  optional value to return if the setting is not set
124
     * @return mixed
125
     */
126
    public static function get($key, $default = null)
127
    {
128
        if (isset(self::$config[$key])) {
129
            return self::$config[$key];
130
        }
131
132
        if (! Str::contains($key, '.')) {
133
            return $default;
134
        }
135
136
        return Arr::get(self::$config, $key, $default);
137
    }
138
139
    /**
140
     * Unset a config setting
141
     * or multiple
142
     *
143
     * @param  string|array  $key
144
     */
145
    public static function forget($key)
146
    {
147
        Arr::forget(self::$config, $key);
148
    }
149
150
    /**
151
     * Get a setting from a device, if that is not set,
152
     * fall back to the global config setting prefixed by $global_prefix
153
     * The key must be the same for the global setting and the device setting.
154
     *
155
     * @param  array  $device  Device array
156
     * @param  string  $key  Name of setting to fetch
157
     * @param  string  $global_prefix  specify where the global setting lives in the global config
158
     * @param  mixed  $default  will be returned if the setting is not set on the device or globally
159
     * @return mixed
160
     */
161
    public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null)
162
    {
163
        if (isset($device[$key])) {
164
            return $device[$key];
165
        }
166
167
        if (isset($global_prefix)) {
168
            $key = "$global_prefix.$key";
169
        }
170
171
        return self::get($key, $default);
172
    }
173
174
    /**
175
     * Get a setting from the $config['os'] array using the os of the given device
176
     *
177
     * @param  string  $os  The os name
178
     * @param  string  $key  period separated config variable name
179
     * @param  mixed  $default  optional value to return if the setting is not set
180
     * @return mixed
181
     */
182
    public static function getOsSetting($os, $key, $default = null)
183
    {
184
        if ($os) {
185
            \LibreNMS\Util\OS::loadDefinition($os);
186
187
            if (isset(self::$config['os'][$os][$key])) {
188
                return self::$config['os'][$os][$key];
189
            }
190
191
            $os_key = "os.$os.$key";
192
            if (self::has($os_key)) {
193
                return self::get($os_key);
194
            }
195
        }
196
197
        return $default;
198
    }
199
200
    /**
201
     * Get the merged array from the global and os settings for the specified key.
202
     * Removes any duplicates.
203
     * When the arrays have keys, os settings take precedence over global settings
204
     *
205
     * @param  string  $os  The os name
206
     * @param  string  $key  period separated config variable name
207
     * @param  array  $default  optional array to return if the setting is not set
208
     * @return array
209
     */
210
    public static function getCombined($os, $key, $default = [])
211
    {
212
        if (! self::has($key)) {
213
            return self::getOsSetting($os, $key, $default);
214
        }
215
216
        if (! isset(self::$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
     */
237
    public static function set($key, $value)
238
    {
239
        Arr::set(self::$config, $key, $value);
240
    }
241
242
    /**
243
     * Save setting to persistent storage.
244
     *
245
     * @param  mixed  $key  period separated config variable name
246
     * @param  mixed  $value
247
     * @return bool if the save was successful
248
     */
249
    public static function persist($key, $value)
250
    {
251
        try {
252
            \App\Models\Config::updateOrCreate(['config_name' => $key], [
253
                'config_name' => $key,
254
                'config_value' => $value,
255
            ]);
256
            Arr::set(self::$config, $key, $value);
257
258
            // delete any children (there should not be any unless it is legacy)
259
            \App\Models\Config::query()->where('config_name', 'like', "$key.%")->delete();
260
261
            return true;
262
        } catch (Exception $e) {
263
            if (class_exists(Log::class)) {
264
                Log::error($e);
265
            }
266
            if (Debug::isEnabled()) {
267
                echo $e;
268
            }
269
270
            return false;
271
        }
272
    }
273
274
    /**
275
     * Forget a key and all it's descendants from persistent storage.
276
     * This will effectively set it back to default.
277
     *
278
     * @param  string  $key
279
     * @return int|false
280
     */
281
    public static function erase($key)
282
    {
283
        self::forget($key);
284
        try {
285
            return \App\Models\Config::withChildren($key)->delete();
286
        } catch (Exception $e) {
287
            return false;
288
        }
289
    }
290
291
    /**
292
     * Check if a setting is set
293
     *
294
     * @param  string  $key  period separated config variable name
295
     * @return bool
296
     */
297
    public static function has($key)
298
    {
299
        if (isset(self::$config[$key])) {
300
            return true;
301
        }
302
303
        if (! Str::contains($key, '.')) {
304
            return false;
305
        }
306
307
        return Arr::has(self::$config, $key);
308
    }
309
310
    /**
311
     * Serialise the whole configuration to json for use in external processes.
312
     *
313
     * @return string
314
     */
315
    public static function toJson()
316
    {
317
        return json_encode(self::$config);
318
    }
319
320
    /**
321
     * Get the full configuration array
322
     *
323
     * @return array
324
     */
325
    public static function getAll()
326
    {
327
        return self::$config;
328
    }
329
330
    /**
331
     * merge the database config with the global config
332
     * Global config overrides db
333
     */
334
    private static function loadDB()
335
    {
336
        if (! Eloquent::isConnected()) {
337
            return;
338
        }
339
340
        try {
341
            \App\Models\Config::get(['config_name', 'config_value'])
342
                ->each(function ($item) {
343
                    Arr::set(self::$config, $item->config_name, $item->config_value);
344
                });
345
        } catch (QueryException $e) {
346
            // possibly table config doesn't exist yet
347
        }
348
349
        // load graph types from the database
350
        self::loadGraphsFromDb(self::$config);
351
    }
352
353
    private static function loadGraphsFromDb(&$config)
354
    {
355
        try {
356
            $graph_types = GraphType::all()->toArray();
357
        } catch (QueryException $e) {
358
            // possibly table config doesn't exist yet
359
            $graph_types = [];
360
        }
361
362
        // load graph types from the database
363
        foreach ($graph_types as $graph) {
364
            $g = [];
365
            foreach ($graph as $k => $v) {
366
                if (strpos($k, 'graph_') == 0) {
367
                    // remove leading 'graph_' from column name
368
                    $key = str_replace('graph_', '', $k);
369
                } else {
370
                    $key = $k;
371
                }
372
                $g[$key] = $v;
373
            }
374
375
            $config['graph_types'][$g['type']][$g['subtype']] = $g;
376
        }
377
    }
378
379
    /**
380
     * Handle defaults that are set programmatically
381
     */
382
    private static function processDefaults()
383
    {
384
        Arr::set(self::$config, 'log_dir', base_path('logs'));
385
        Arr::set(self::$config, 'distributed_poller_name', php_uname('n'));
386
387
        // set base_url from access URL
388
        if (isset($_SERVER['SERVER_NAME']) && isset($_SERVER['SERVER_PORT'])) {
389
            $port = $_SERVER['SERVER_PORT'] != 80 ? ':' . $_SERVER['SERVER_PORT'] : '';
390
            // handle literal IPv6
391
            $server = Str::contains($_SERVER['SERVER_NAME'], ':') ? "[{$_SERVER['SERVER_NAME']}]" : $_SERVER['SERVER_NAME'];
392
            Arr::set(self::$config, 'base_url', "http://$server$port/");
393
        }
394
395
        // graph color copying
396
        Arr::set(self::$config, 'graph_colours.mega', array_merge(
397
            (array) Arr::get(self::$config, 'graph_colours.psychedelic', []),
398
            (array) Arr::get(self::$config, 'graph_colours.manycolours', []),
399
            (array) Arr::get(self::$config, 'graph_colours.default', []),
400
            (array) Arr::get(self::$config, 'graph_colours.mixed', [])
401
        ));
402
    }
403
404
    /**
405
     * Process the config after it has been loaded.
406
     * Make sure certain variables have been set properly and
407
     */
408
    private static function processConfig()
409
    {
410
        // If we're on SSL, let's properly detect it
411
        if (
412
            isset($_SERVER['HTTPS']) ||
413
            (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
414
        ) {
415
            self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url')));
416
        }
417
418
        self::set('base_url', Str::finish(self::get('base_url'), '/'));
419
420
        if (! self::get('email_from')) {
421
            self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>');
422
        }
423
424
        // Define some variables if they aren't set by user definition in config_definitions.json
425
        self::set('applied_site_style', self::get('site_style'));
426
        self::setDefault('html_dir', '%s/html', ['install_dir']);
427
        self::setDefault('rrd_dir', '%s/rrd', ['install_dir']);
428
        self::setDefault('mib_dir', '%s/mibs', ['install_dir']);
429
        self::setDefault('log_dir', '%s/logs', ['install_dir']);
430
        self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']);
431
        self::setDefault('plugin_dir', '%s/plugins', ['html_dir']);
432
        self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp');
433
        self::setDefault('irc_nick', '%s', ['project_name']);
434
        self::setDefault('irc_chan.0', '##%s', ['project_id']);
435
        self::setDefault('page_title_suffix', '%s', ['project_name']);
436
//        self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']);  // FIXME email_from set because alerting config
437
438
        // deprecated variables
439
        self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile');
440
        self::deprecatedVariable('fping_options.millisec', 'fping_options.interval');
441
        self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf');
442
        self::deprecatedVariable('discovery_modules.toner', 'discovery_modules.printer-supplies');
443
        self::deprecatedVariable('poller_modules.toner', 'poller_modules.printer-supplies');
444
        self::deprecatedVariable('discovery_modules.cisco-sla', 'discovery_modules.slas');
445
        self::deprecatedVariable('poller_modules.cisco-sla', 'poller_modules.slas');
446
        self::deprecatedVariable('oxidized.group', 'oxidized.maps.group');
447
448
        $persist = Eloquent::isConnected();
449
        // make sure we have full path to binaries in case PATH isn't set
450
        foreach (['fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute', 'traceroute6'] as $bin) {
451
            if (! is_executable(self::get($bin))) {
0 ignored issues
show
Bug introduced by
It seems like self::get($bin) can also be of type null; however, parameter $filename of is_executable() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

451
            if (! is_executable(/** @scrutinizer ignore-type */ self::get($bin))) {
Loading history...
452
                if ($persist) {
453
                    self::persist($bin, self::locateBinary($bin));
454
                } else {
455
                    self::set($bin, self::locateBinary($bin));
456
                }
457
            }
458
        }
459
460
        self::populateTime();
461
462
        // populate legacy DB credentials, just in case something external uses them.  Maybe remove this later
463
        self::populateLegacyDbCredentials();
464
    }
465
466
    /**
467
     * Set default values for defaults that depend on other settings, if they are not already loaded
468
     *
469
     * @param  string  $key
470
     * @param  string  $value  value to set to key or vsprintf() format string for values below
471
     * @param  array  $format_values  array of keys to send to vsprintf()
472
     */
473
    private static function setDefault($key, $value, $format_values = [])
474
    {
475
        if (! self::has($key)) {
476
            if (is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
477
                $format_values = array_map('self::get', $format_values);
478
                self::set($key, vsprintf($value, $format_values));
479
            } else {
480
                self::set($key, $value);
481
            }
482
        }
483
    }
484
485
    /**
486
     * Copy data from old variables to new ones.
487
     *
488
     * @param  string  $old
489
     * @param  string  $new
490
     */
491
    private static function deprecatedVariable($old, $new)
492
    {
493
        if (self::has($old)) {
494
            if (Debug::isEnabled()) {
495
                echo "Copied deprecated config $old to $new\n";
496
            }
497
            self::set($new, self::get($old));
498
        }
499
    }
500
501
    /**
502
     * Locate the actual path of a binary
503
     *
504
     * @param  string  $binary
505
     * @return mixed
506
     */
507
    public static function locateBinary($binary)
508
    {
509
        if (! Str::contains($binary, '/')) {
510
            $output = `whereis -b $binary`;
511
            $list = trim(substr($output, strpos($output, ':') + 1));
512
            $targets = explode(' ', $list);
513
            foreach ($targets as $target) {
514
                if (is_executable($target)) {
515
                    return $target;
516
                }
517
            }
518
        }
519
520
        return $binary;
521
    }
522
523
    private static function populateTime()
524
    {
525
        $now = time();
526
        $now -= $now % 300;
527
        self::set('time.now', $now);
528
        self::set('time.onehour', $now - 3600); // time() - (1 * 60 * 60);
529
        self::set('time.fourhour', $now - 14400); // time() - (4 * 60 * 60);
530
        self::set('time.sixhour', $now - 21600); // time() - (6 * 60 * 60);
531
        self::set('time.twelvehour', $now - 43200); // time() - (12 * 60 * 60);
532
        self::set('time.day', $now - 86400); // time() - (24 * 60 * 60);
533
        self::set('time.twoday', $now - 172800); // time() - (2 * 24 * 60 * 60);
534
        self::set('time.week', $now - 604800); // time() - (7 * 24 * 60 * 60);
535
        self::set('time.twoweek', $now - 1209600); // time() - (2 * 7 * 24 * 60 * 60);
536
        self::set('time.month', $now - 2678400); // time() - (31 * 24 * 60 * 60);
537
        self::set('time.twomonth', $now - 5356800); // time() - (2 * 31 * 24 * 60 * 60);
538
        self::set('time.threemonth', $now - 8035200); // time() - (3 * 31 * 24 * 60 * 60);
539
        self::set('time.sixmonth', $now - 16070400); // time() - (6 * 31 * 24 * 60 * 60);
540
        self::set('time.year', $now - 31536000); // time() - (365 * 24 * 60 * 60);
541
        self::set('time.twoyear', $now - 63072000); // time() - (2 * 365 * 24 * 60 * 60);
542
    }
543
544
    public static function populateLegacyDbCredentials()
545
    {
546
        $db = config('database.default');
547
548
        self::set('db_host', config("database.connections.$db.host", 'localhost'));
549
        self::set('db_name', config("database.connections.$db.database", 'librenms'));
550
        self::set('db_user', config("database.connections.$db.username", 'librenms'));
551
        self::set('db_pass', config("database.connections.$db.password"));
552
        self::set('db_port', config("database.connections.$db.port", 3306));
553
        self::set('db_socket', config("database.connections.$db.unix_socket"));
554
    }
555
}
556