Completed
Push — develop ( 485732...a8a327 )
by Tony
03:17
created

Settings::get()   D

Complexity

Conditions 9
Paths 1

Size

Total Lines 39
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 39
ccs 16
cts 16
cp 1
rs 4.909
cc 9
eloc 16
nc 1
nop 2
crap 9
1
<?php
2
/*
3
 * Copyright (C) 2016 Tony Murray <[email protected]>
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
/**
18
 * Settings.php
19
 *
20
 * @package    LibreNMS
21
 * @author     Tony Murray <[email protected]>
22
 * @copyright  2016 Tony Murray
23
 * @license    @license http://opensource.org/licenses/GPL-3.0 GNU Public License v3 or later
24
 */
25
namespace App;
26
27
28
use App\Models\DbConfig;
29
use Cache;
30
use Config;
31
use Illuminate\Contracts\Config\Repository as ConfigContract;
32
33
// adds the possibility to replace the default Config facade
34
35
class Settings implements ConfigContract
36
{
37
    /**
38
     * The tag for the cache.
39
     *
40
     * @var string
41
     */
42
    public static $cache_tag = "Settings";
43
    /**
44
     * The amount of time in minutes to store values in cache
45
     *
46
     * @var int
47
     */
48
    private $cache_time;
49
50
    /**
51
     * Settings constructor.
52
     */
53 45
    public function __construct()
54
    {
55 45
        $this->cache_time = env('CACHE_LIFETIME', 60);
56 45
    }
57
58
    /**
59
     * Set a key value pair into the Settings store.
60
     *
61
     * @param string $key A . separated path to this setting
62
     * @param array|null $value A value or an array. If value is an array it will be converted to a . separate path(s) concatinated onto the given key
63
     * @throws \Exception
64
     */
65 26
    public function set($key, $value = null)
66
    {
67 26
        if (is_array($value)) {
68
            // repeat until value contains no arrays
69 14
            foreach ($value as $k => $v) {
70 14
                if (!empty($key)) {
71 13
                    if (is_string($k) && !str_contains($k, '.') && DbConfig::exactKey($key)->exists() && DbConfig::key($key)->count() == 1) {
0 ignored issues
show
Bug introduced by
The method exists does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Bug introduced by
The method count does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
72
                        // check that we aren't trying to set an array onto an existing value only setting
73 1
                        throw new \Exception("Attempting to set array value to existing non-array value at the key '".$key."'");
74
                    }
75
                    else {
76
                        // we are not at the leaf yet, add this chunk to the key and recurse
77 12
                        $this->set($key.'.'.$k, $v);
78
                    }
79 12
                }
80
                else {
81
                    // a leaf, recurse one last time
82 2
                    $this->set($k, $v);
83
                }
84 13
            }
85 13
        }
86
        else {
87
            // make sure we can save this
88 26
            if ($this->isReadOnly($key)) {
89 1
                throw new \Exception("The setting '".$key."' is read only");
90
            }
91
92
            // flush the cache and save the value in db and cache
93 25
            $this->flush($key);
94 25
            DbConfig::updateOrCreate(['config_name' => $key], ['config_value' => $value]);
95 25
            Cache::tags(self::$cache_tag)->put($key, $value, $this->cache_time);
96
        }
97
98 25
    }
99
100
    /**
101
     * Get a value from the Settings store.
102
     *
103
     * @param string $key A full or partial . separated key.
104
     * @param null $default If the key isn't found, return this value. By default undefined keys return null.
105
     * @return mixed If the $key is a full path, a bare value will be returned.  If it is a partial path, a nested array will be retuned.
106
     */
107
    public function get($key, $default = null)
108
    {
109
        // return value from cache or fetch it and return it
110 40
        return Cache::tags(self::$cache_tag)->remember($key, $this->cache_time, function() use ($key, $default) {
111
112 33
            // fetch the values from storage
113 4
            if (Config::has('config.'.$key)) {
114 4
                $config_data = Config::get('config.'.$key, $default);
115
            }
116 3
            $db_data = DbConfig::key($key)->get(['config_name', 'config_value']);
0 ignored issues
show
Bug introduced by
The method get does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
117
118 1
            if (count($db_data) == 1 && $db_data->first()->config_name == $key) {
119
                // return a value if we are getting one item and it is the requested item (not recursing)
120
                return $db_data->first()->config_value;
121 30
            }
122
            elseif (count($db_data) >= 1) {
123 30
                // convert the collection to an array
124
                $result = self::collectionToArray($db_data, $key);
125 1
126
                // if we have config_data, merge them
127 29
                if (isset($config_data)) {
128
                    return array_replace_recursive($config_data, $result);
129 11
                }
130
                else {
131
                    return $result;
132 11
                }
133 1
            }
134
135
            // fall back to config.php/defaults.php
136 10
            if (isset($config_data) && (!is_array($config_data) || count($db_data) == 0)) {
137
                // return the value from config.php if it is a value
138
                return $config_data;
139
            }
140 20
141 40
142
            // we couldn't find the key, return the default
143
            return $default;
144
        });
145
    }
146
147
    /**
148
     * Convert an Eloquent Collection into a nested array
149
     *
150
     * @param $data \Illuminate\Database\Eloquent\Collection The Collection.
151 12
     * @param string $prefix Path to prepend. Do not include trailing .
152
     * @return array The resulting nested array.
153 12
     */
154 12
    private static function collectionToArray($data, $prefix = "")
155 12
    {
156 12
        $tree = array();
157 11
        foreach ($data as $item) {
158 11
            $key = $item->config_name;
159 12
            if (starts_with($key, $prefix)) {
160
                $key = substr($key, strlen($prefix));
161 12
            }
162 12
            $parts = explode('.', trim($key, '.'));
163 12
164 12
            $temp = &$tree;
165 12
            foreach ($parts as $part) {
166 12
                $temp = &$temp[$part];
167 12
            }
168 12
            $temp = $item->config_value;
169
            unset($temp);
170
        }
171
        return $tree;
172
    }
173
174
    /**
175
     * Check if the key is defined in the Settings store.
176
     *
177 2
     * @param string $key Only full paths will return true.
178
     * @return bool
179 2
     */
180
    public function has($key)
181
    {
182
        return (Cache::tags(self::$cache_tag)->has($key) || Config::has('config.'.$key) || DbConfig::key($key)->exists());
0 ignored issues
show
Bug introduced by
The method exists does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
183
    }
184
185
    /**
186
     * Check if the key is read only.
187
     * When the user is not a global admin.
188
     * Currently, $key is unused
189
     *
190 27
     * @param string $key The path to check
191
     * @return boolean false or the source: config | auth
192 27
     */
193 2
    public function isReadOnly($key)
194
    {
195 26
        $user = \Auth::user();
196 26
        return (is_null($user) || !$user->isAdmin());
197 26
    }
198
199 1
    /**
200
     * Forget a key and all children
201
     * This cannot forget variables set in config.php
202
     *
203
     * @param string $key Explicit key to forget
204
     */
205
    public function forget($key)
206
    {
207
        // Cannot remove from config
208
209 3
        $count = DbConfig::key($key)->count();
0 ignored issues
show
Bug introduced by
The method count does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
210
        if ($count == 1) {
211
            $this->flush($key);
212
        }
213 3
        else {
214 3
            $this->flush(); // possible optimization: selective flush
215 2
        }
216 2
217
        DbConfig::key($key)->delete();
218 3
    }
219
220
    /**
221 3
     * Get all settings defined in the Settings store.
222 3
     *
223
     * @return array A nested array of all settings.
224
     */
225
    public function all()
226
    {
227
        // no caching :(
228
        $config_settings = Config::all()['config'];
229 1
        $db_settings = self::collectionToArray(DbConfig::all());
230
        return array_replace_recursive($config_settings, $db_settings);
231
    }
232 1
233 1
    // ---- Local Utility functions ----
234 1
235
    /**
236
     * Clear the settings cache.
237
     * If path is set, only clear the path and it's parents.
238
     * This will not clear children.
239
     *
240
     * @param string $key The path to clear.
241
     */
242
    public function flush($key = null)
243
    {
244
        if (is_null($key)) {
245
            // Clear all cache
246 25
            Cache::tags(self::$cache_tag)->flush();
247
        }
248 25
        else {
249
            // Clear specific path
250 7
            $path = [];
251 7
            foreach (explode('.', $key) as $element) {
252
                $path[] = $element;
253
                Cache::tags(self::$cache_tag)->forget(join('.', $path));
254 25
            }
255 25
        }
256 25
    }
257 25
258 25
    /**
259
     * Prepend a value onto an array configuration value.
260 25
     *
261
     * @param  string $key
262
     * @param  mixed $value
263
     * @return void
264
     */
265
    public function prepend($key, $value)
266
    {
267
        $var = $this->get($key);
268
269 1
        if (is_array($var)) {
270
            $this->forget($key);
271 1
            array_unshift($var, $value);
272
            $this->set($key, $var);
273 1
        }
274 1
        else {
275 1
            $arr = [$value];
276 1
            if (!is_null($var)) {
277 1
                $arr[] = $var;
278
            }
279 1
280 1
            $this->forget($key);
281 1
            $this->set($key, $arr);
282 1
        }
283
    }
284 1
285 1
    /**
286
     * Push a value onto an array configuration value.
287 1
     *
288
     * @param  string $key
289
     * @param  mixed $value
290
     * @return void
291
     */
292
    public function push($key, $value)
293
    {
294
        $var = $this->get($key);
295
        if (is_array($var)) {
296 1
            $var[] = $value;
297
            $this->set($key, $var);
298 1
        }
299 1
        else {
300 1
            $arr = array();
301 1
            if (!is_null($var)) {
302 1
                $arr[] = $var;
303
            }
304 1
            $arr[] = $value;
305 1
306 1
            $this->forget($key);
307 1
            $this->set($key, $arr);
308 1
        }
309
    }
310
}
311