Completed
Pull Request — develop (#79)
by Tony
13:58
created

Settings::set()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 34
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9

Importance

Changes 7
Bugs 3 Features 0
Metric Value
c 7
b 3
f 0
dl 0
loc 34
ccs 17
cts 17
cp 1
rs 4.909
cc 9
eloc 16
nc 6
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 28
    public function __construct()
54
    {
55 28
        $this->cache_time = env('CACHE_LIFETIME', 60);
56 28
    }
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 25
    public function set($key, $value = null)
66
    {
67 25
        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) {
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 25
            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 24
            $this->flush($key);
94 24
            DbConfig::updateOrCreate(['config_name' => $key], ['config_value' => $value]);
95 24
            Cache::tags(self::$cache_tag)->put($key, $value, $this->cache_time);
96
        }
97
98 24
    }
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 24
        return Cache::tags(self::$cache_tag)->remember($key, $this->cache_time, function () use ($key, $default) {
111
            // fetch the value from config.php first
112 18
            if (Config::has('config.' . $key)) {
113 4
                $config_data = Config::get('config.' . $key, $default);
114 4
                if (!is_array($config_data)) {
115
                    // return the value from config.php if it is a value
116 3
                    return $config_data;
117
                }
118 1
            }
119
120
            // fetch the value from the database
121 15
            $db_data = DbConfig::key($key)->get(['config_name', 'config_value']);
122
123 15
            if (count($db_data) == 1 && $db_data->first()->config_name == $key) {
124
                // return a value if we are getting one item
125 1
                return $db_data->first()->config_value;
126
            }
127 14
            elseif (count($db_data) >= 1) {
128
                // convert the collection to an array
129 11
                $result = self::collectionToArray($db_data, $key);
130
131
                // if we have config_data, merge them
132 11
                if (isset($config_data)) {
133 1
                    return array_replace_recursive($result, $config_data);
134
                }
135
                else {
136 10
                    return $result;
137
                }
138
            }
139
            // we couldn't find the key, return the default
140 5
            return $default;
141 24
        });
142
    }
143
144
    /**
145
     * Convert an Eloquent Collection into a nested array
146
     *
147
     * @param $data \Illuminate\Database\Eloquent\Collection The Collection.
148
     * @param string $prefix Path to prepend. Do not include trailing .
149
     * @return array The resulting nested array.
150
     */
151 12
    private static function collectionToArray($data, $prefix = "")
152
    {
153 12
        $tree = array();
154 12
        foreach ($data as $item) {
155 12
            $key = $item->config_name;
156 12
            if (starts_with($key, $prefix)) {
157 11
                $key = substr($key, strlen($prefix));
158 11
            }
159 12
            $parts = explode('.', trim($key, '.'));
160
161 12
            $temp = &$tree;
162 12
            foreach ($parts as $part) {
163 12
                $temp = &$temp[$part];
164 12
            }
165 12
            $temp = $item->config_value;
166 12
            unset($temp);
167 12
        }
168 12
        return $tree;
169
    }
170
171
    /**
172
     * Check if the key is defined in the Settings store.
173
     *
174
     * @param string $key Only full paths will return true.
175
     * @return bool
176
     */
177 2
    public function has($key)
178
    {
179 2
        return (Cache::tags(self::$cache_tag)->has($key) || Config::has('config.' . $key) || DbConfig::key($key)->exists());
180
    }
181
182
    /**
183
     * Check if the key is read only.  This is when the setting is defined in config.php.
184
     *
185
     * @param string $key The path to check
186
     * @return bool
187
     */
188 25
    public function isReadOnly($key)
189
    {
190 25
        return Config::has('config.' . $key);
191
    }
192
193
    /**
194
     * Forget a key and all children
195
     * This cannot forget variables set in config.php
196
     *
197
     * @param $key string Explicit key to forget
198
     */
199 3
    public function forget($key)
200
    {
201
        // Cannot remove from config
202
203 3
        $count = DbConfig::key($key)->count();
204 3
        if ($count == 1) {
205 2
            $this->flush($key);
206 2
        }
207
        else {
208 3
            $this->flush(); // possible optimization: selective flush
209
        }
210
211 3
        DbConfig::key($key)->delete();
212 3
    }
213
214
    /**
215
     * Get all settings defined in the Settings store.
216
     *
217
     * @return array A nested array of all settings.
218
     */
219 1
    public function all()
220
    {
221
        // no caching :(
222 1
        $config_settings = Config::all()['config'];
223 1
        $db_settings = self::collectionToArray(DbConfig::all());
224 1
        return array_replace_recursive($config_settings, $db_settings);
225
    }
226
227
    // ---- Local Utility functions ----
228
229
    /**
230
     * Clear the settings cache.
231
     * If path is set, only clear the path and it's parents.
232
     * This will not clear children.
233
     *
234
     * @param string $key The path to clear.
235
     */
236 24
    public function flush($key = null)
237
    {
238 24
        if (is_null($key)) {
239
            // Clear all cache
240 7
            Cache::tags(self::$cache_tag)->flush();
241 7
        }
242
        else {
243
            // Clear specific path
244 24
            $path = [];
245 24
            foreach (explode('.', $key) as $element) {
246 24
                $path[] = $element;
247 24
                Cache::tags(self::$cache_tag)->forget(join('.', $path));
248 24
            }
249
        }
250 24
    }
251
252
    /**
253
     * Prepend a value onto an array configuration value.
254
     *
255
     * @param  string $key
256
     * @param  mixed $value
257
     * @return void
258
     */
259 1
    public function prepend($key, $value)
260
    {
261 1
        $var = $this->get($key);
262
263 1
        if (is_array($var)) {
264 1
            $this->forget($key);
265 1
            array_unshift($var, $value);
266 1
            $this->set($key, $var);
267 1
        }
268
        else {
269 1
            $arr = [$value];
270 1
            if (!is_null($var)) {
271 1
                $arr[] = $var;
272 1
            }
273
274 1
            $this->forget($key);
275 1
            $this->set($key, $arr);
276
        }
277 1
    }
278
279
    /**
280
     * Push a value onto an array configuration value.
281
     *
282
     * @param  string $key
283
     * @param  mixed $value
284
     * @return void
285
     */
286 1
    public function push($key, $value)
287
    {
288 1
        $var = $this->get($key);
289 1
        if (is_array($var)) {
290 1
            $var[] = $value;
291 1
            $this->set($key, $var);
292 1
        }
293
        else {
294 1
            $arr = array();
295 1
            if (!is_null($var)) {
296 1
                $arr[] = $var;
297 1
            }
298 1
            $arr[] = $value;
299
300 1
            $this->forget($key);
301 1
            $this->set($key, $arr);
302
        }
303 1
    }
304
}
305