Issues (9)

src/Setting.php (3 issues)

1
<?php
2
3
namespace Ottosmops\Settings;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Facades\Cache;
7
use Illuminate\Support\Facades\Validator;
8
use Illuminate\Validation\ValidationException;
9
10
use Ottosmops\Settings\Exceptions\NoKeyIsFound;
11
12
class Setting extends Model
13
{
14
    protected $keyType = 'string';
15
16
    protected $guarded = [];
17
18
    public $timestamps = false;
19
20
    public $incrementing = false;
21
22
    protected $primaryKey = 'key';
23
24
    protected $table;
25
26
    static $all_settings;
27
28
    protected $casts = [
29
        'editable' => 'boolean',
30
        'value' => 'array',
31
        'default' => 'array'
32
    ];
33
34 42
    protected function asJson($value)
35
    {
36 42
        if (is_string($value) && $this->type == 'regex') {
37 3
            $value = str_replace('\\', 'ç€π', $value);
38 3
            $value = str_replace('/', '@ƻ', $value);
39
        }
40 42
        return json_encode($value, JSON_UNESCAPED_UNICODE);
41
    }
42
43 66
    public function setTypeAttribute($value)
44
    {
45 66
        switch ($value) {
46 66
            case 'arr':
47 63
            case 'array':
48 6
                $this->attributes['type'] = 'array';
49 6
                break;
50 63
            case 'int':
51 60
            case 'integer':
52 12
                $this->attributes['type'] = 'integer';
53 12
                break;
54 54
            case 'bool':
55 51
            case 'boolean':
56 12
                $this->attributes['type'] = 'boolean';
57 12
                break;
58 42
            case 'regex':
59 3
                $this->attributes['type'] = 'regex';
60 3
                break;
61 39
            case 'string':
62 36
                $this->attributes['type'] = 'string';
63 36
                break;
64
            default:
65 3
                throw new \UnexpectedValueException($value);
66
        }
67 63
    }
68
69 66
    public function __construct(array $attributes = [])
70
    {
71 66
        $this->table = config('settings.table', 'settings');
72 66
        parent::__construct($attributes);
73 66
    }
74
75
    /**
76
     * Get the editable status
77
     *
78
     * @param  string  $value
79
     * @return bool
80
     */
81 63
    public function getEditableAttribute($value)
82
    {
83 63
        if (!isset($value)) {
84 60
            return true;
85
        }
86
87 6
        return (bool) $value;
88
    }
89
90
    /**
91
     * Check if setting is editable
92
     *
93
     * @param  string  $value
94
     * @return bool
95
     */
96 3
    public static function isEditable(string $key) : bool
97
    {
98 3
        return Setting::where('key', $key)->first()->editable;
99
    }
100
101
    /**
102
     * Cast a value to the expected type ($this->type)
103
     * Possible types: array, integer, boolean, string
104
     *
105
     * @param  mixed $value
106
     * @return mixed
107
     */
108 57
    public function getValueAttribute($value)
109
    {
110 57
        if ($value === null) {
111 45
            return null;
112
        }
113
114 39
        switch ($this->type) {
115 39
            case 'arr':
116 39
            case 'array':
117 6
                return json_decode($value, true);
118 36
            case 'int':
119 36
            case 'integer':
120 6
                return intval($value);
121 33
            case 'bool':
122 33
            case 'boolean':
123 12
                if ($value === "false") {
124 6
                    return false;
125
                }
126 12
                return boolval($value);
127 21
            case 'regex':
128 3
                $value = str_replace('ç€π', '\\', $value);
129 3
                $value = str_replace('@ƻ', '/', $value);
130 3
                return trim($value, '"');
131 18
            case 'string':
132 18
                $value = json_decode($value, true);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
133
            default:
134 18
                return trim($value, '"');
135
        }
136
    }
137
138 6
    public function getValueAsStringAttribute()
139
    {
140 6
        if ($this->value === null) {
141
            return '';
142
        }
143
144 6
        $type = $this->type ?: gettype($this->value);
145 6
        $value = $this->value;
146 6
        switch ($type) {
147 6
            case 'array':
148 6
            case 'object':
149 3
                return json_encode($value, JSON_UNESCAPED_UNICODE);
150 6
            case 'integer':
151 3
                return (string) $value;
152 6
            case 'boolean':
153 3
                if ($value === false) {
154
                    return "false";
155
                }
156 3
                return "true";
157 3
            case 'regex':
158
                $value = str_replace('ç€π', '\\', $value);
159
                $value = str_replace('@ƻ', '/', $value);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
160 3
            case 'string':
161 3
                $value =  trim($value, '"');
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
162
            default:
163 3
                return (string) $value;
164
        }
165
    }
166
167
    /**
168
     * Get a type casted setting value
169
     *
170
     * @param  string $key
171
     * @param  mixed  $default is returned if value is empty (except boolean false)
172
     * @return mixed
173
     */
174 39
    public static function getValue(string $key, $default = null)
175
    {
176 39
        if (!self::has($key)) {
177 9
            throw new NoKeyIsFound();
178
        }
179
180 30
        if (self::hasValue($key)) {
181 30
            return \Ottosmops\Settings\Setting::allSettings()[$key]['value'];
182
        }
183
184 3
        return $default;
185
    }
186
187 6
    public static function getValueAsString(string $key, $default = null)
188
    {
189 6
        if (!self::has($key)) {
190
            throw new NoKeyIsFound();
191
        }
192
193 6
        $setting = static::where('key', $key)->first();
194
195 6
        return $setting->valueAsString ?: $default;
196
    }
197
198
    /**
199
    * Check if setting exists
200
    *
201
    * @param $key
202
    * @return bool
203
    */
204 57
    public static function has(string $key) : bool
205
    {
206 57
        return (boolean) isset(self::allSettings()[$key]);
207
    }
208
209
    /**
210
     * If a setting has a value (also boolean false, integer 0 and an empty string are values)
211
     *
212
     * @param  string  $key
213
     * @return boolean
214
     */
215 33
    public static function hasValue(string $key) : bool
216
    {
217 33
        if (self::has($key) && isset(Setting::allSettings()[$key]['value'])) {
218 33
            $value = Setting::allSettings()[$key]['value'];
219 33
            return !empty($value) || $value === false || $value === 0 || $value === '';
220
        }
221
222 6
        return false;
223
    }
224
225
    /**
226
     * Set a new value
227
     * @param string  $key
228
     * @param mixed  $value    // string, integer, boolean or array
229
     * @param boolean
230
     */
231 33
    public static function setValue(string $key, $value = null, $validate = true)
232
    {
233 33
        if (!self::has($key)) {
234
            throw new NoKeyIsFound();
235
        }
236
237 33
        $setting = self::find($key);
238
239 33
        if ($setting->type === 'boolean' && is_string($value)) {
240
            $value = ($value === 'false') ? false : $value;
241
            $value = ($value === 'true')  ? true  : $value;
242
        }
243 33
        if ($setting->type === 'integer' && is_string($value) && ctype_digit($value)) {
244
            $value = (int) $value;
245
        }
246
247 33
        if ($validate && !$setting->validateNewValue($value)) {
248 6
            throw new ValidationException(null);
249
        }
250
251 27
        $setting->value = $value;
252
253 27
        return $setting->save();
254
    }
255
256
257
    /**
258
     * Remove a setting
259
     *
260
     * @param $key
261
     * @return bool
262
     */
263 6
    public static function remove(string $key)
264
    {
265 6
        if (self::has($key)) {
266 3
            return self::find($key)->delete();
267
        }
268
269 3
        throw new NoKeyIsFound();
270
    }
271
272
    /**
273
     * Get all the settings
274
     *
275
     * @return mixed
276
     */
277 57
    public static function allSettings() : array
278
    {
279 57
        if (! static::$all_settings) {
280
            static::$all_settings = Cache::rememberForever('settings.all', function () {
281 57
                return self::all()->keyBy('key')->toArray();
282 57
            });
283
        }
284
285 57
        return static::$all_settings;
286
    }
287
288
    /**
289
     * Helper function: Validate a value against its type and its rules.
290
     * @param  mixed $value
291
     * @return bool
292
     */
293 33
    public function validateNewValue($value) : bool
294
    {
295 33
        if ($this->type === 'regex') {
296 3
            $validator = Validator::make([$this->key => $value], [$this->key => 'string']);
297 3
            return !$validator->fails();
298
        }
299 30
        $validator = Validator::make([$this->key => $value], self::getValidationRules());
300
301 30
        return !$validator->fails();
302
    }
303
304
    /**
305
     * Get the validation rules for setting fields
306
     *
307
     * @return array
308
     */
309 33
    public static function getValidationRules() : array
310
    {
311 33
        if ('mysql' === \DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
312
            return Cache::rememberForever('settings.rules', function () {
313
                return Setting::select(\DB::raw('concat_ws("|", rules, type) as rules'))
314
                            ->select('key')
315
                            ->get()
316
                            ->toArray();
317
            });
318
        }
319
320
        return Cache::rememberForever('settings.rules', function () {
321 33
            return Setting::select(\DB::raw("printf('%s|%s', rules, type) as rules, `key`"))
322 33
                            ->pluck('rules', 'key')
323 33
                            ->toArray();
324 33
        });
325
    }
326
327
    /**
328
     * Flush the cache
329
     */
330 63
    public static function flushCache()
331
    {
332 63
        Cache::forget('settings.all');
333 63
        Cache::forget('settings.rules');
334 63
    }
335
336
    /**
337
     * The "booting" method of the model.
338
     *
339
     * @return void
340
     */
341 66
    protected static function boot()
342
    {
343 66
        parent::boot();
344
345
        static::deleted(function () {
346 3
            self::flushCache();
347 3
            static::$all_settings = [];
348 66
        });
349
        static::updated(function () {
350 33
            self::flushCache();
351 33
            static::$all_settings = [];
352 66
        });
353
        static::created(function () {
354 63
            self::flushCache();
355 63
            static::$all_settings = [];
356 66
        });
357 66
    }
358
}
359