Test Failed
Push — develop ( 6646cf...aabacf )
by Paul
12:11 queued 03:42
created

OptionManager   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Test Coverage

Coverage 70.09%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 50
eloc 130
c 2
b 0
f 0
dl 0
loc 254
ccs 82
cts 117
cp 0.7009
rs 8.4

18 Methods

Rating   Name   Duplication   Size   Complexity  
A all() 0 7 2
A __call() 0 12 3
A wp() 0 4 1
A clean() 0 13 2
A previous() 0 13 4
A updateVersion() 0 6 2
A databaseKey() 0 10 3
A flushSettingsCache() 0 12 4
A mergeDefaults() 0 13 3
A reset() 0 10 2
A json() 0 5 1
A replace() 0 11 3
A normalize() 0 11 2
A set() 0 10 2
B restoreOrphanedSettings() 0 20 7
A kses() 0 10 2
A get() 0 10 2
A databaseKeys() 0 17 5

How to fix   Complexity   

Complex Class

Complex classes like OptionManager 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 OptionManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Database;
4
5
use GeminiLabs\SiteReviews\Helper;
6
use GeminiLabs\SiteReviews\Helpers\Arr;
7
use GeminiLabs\SiteReviews\Helpers\Cast;
8
use GeminiLabs\SiteReviews\Helpers\Str;
9
use GeminiLabs\SiteReviews\Modules\Migrate;
10
11
/**
12
 * @method array  getArray(string $path = '', $fallback = [])
13
 * @method bool   getBool(string $path = '', $fallback = false)
14
 * @method float  getFloat(string $path = '', $fallback = 0.0)
15
 * @method int    getInt(string $path = '', $fallback = 0)
16
 * @method string getString(string $path = '', $fallback = '')
17
 */
18
class OptionManager
19
{
20
    /**
21
     * @return mixed
22
     */
23 29
    public function __call(string $method, array $args = [])
24
    {
25 29
        if (!str_starts_with($method, 'get')) {
26
            throw new \BadMethodCallException("Method [$method] does not exist.");
27
        }
28 29
        $cast = strtolower((string) substr($method, 3));
29 29
        if (!in_array($cast, ['array', 'bool', 'float', 'int', 'string'])) {
30
            throw new \BadMethodCallException("Method [$method] does not exist.");
31
        }
32 29
        $path = Arr::getAs('string', $args, 0);
33 29
        $fallback = Arr::get($args, 1);
34 29
        return call_user_func([$this, 'get'], $path, $fallback, $cast);
35
    }
36
37 67
    public function all(): array
38
    {
39 67
        $settings = Arr::consolidate(glsr()->retrieve('settings'));
40 67
        if (empty($settings)) {
41
            $settings = $this->reset();
42
        }
43 67
        return $settings;
44
    }
45
46 123
    public function clean(array $data = []): array
47
    {
48 123
        $settings = $this->kses($data);
49 123
        if (!empty(glsr()->settings)) { // access the property directly to prevent an infinite loop
50 123
            $savedSettings = $settings;
51
            $defaults = glsr()->defaults(); // @phpstan-ignore-line
52 123
            $defaults = Arr::flatten($defaults);
53 123
            $settings = Arr::flatten($settings);
54
            $settings = shortcode_atts($defaults, $settings);
55
            $settings = Arr::unflatten($settings);
56
            $settings = $this->restoreOrphanedSettings($settings, $savedSettings);
57
        }
58 123
        return $settings;
59
    }
60 123
61 123
    public static function databaseKey(?int $version = null): string
62 123
    {
63 123
        $versions = static::databaseKeys();
64 123
        if (null === $version) {
65 123
            $version = glsr()->version('major');
66 123
        }
67 123
        if (array_key_exists($version, $versions)) {
68 123
            return $versions[$version];
69 123
        }
70
        return '';
71 123
    }
72
73
    public static function databaseKeys(): array
74 123
    {
75
        $keys = [];
76
        $slug = Str::snakeCase(glsr()->id);
77 8
        $version = intval(glsr()->version('major')) + 1;
78
        while (--$version) {
79 8
            if ($version >= 7) {
80 8
                $keys[$version] = $slug; // remove version from settings key in versions >= 7.0
81 8
            } elseif (1 === $version) {
82 8
                $keys[$version] = sprintf('geminilabs_%s_settings', $slug);
83 8
            } elseif (2 === $version) {
84 8
                $keys[$version] = sprintf('geminilabs_%s-v%s', $slug, $version);
85
            } else {
86
                $keys[$version] = sprintf('%s_v%s', $slug, $version);
87 8
            }
88 8
        }
89
        return $keys;
90
    }
91
92
    public static function flushSettingsCache(): void
93
    {
94
        $alloptions = wp_load_alloptions(true);
95
        $flushed = false;
96
        foreach (static::databaseKeys() as $option) {
97 67
            if (isset($alloptions[$option])) {
98
                unset($alloptions[$option]);
99 67
                $flushed = true;
100 67
            }
101 67
        }
102 67
        if ($flushed) {
103 67
            wp_cache_set('alloptions', $alloptions, 'options');
104
        }
105 67
    }
106
107
    /**
108
     * @param mixed $fallback
109
     *
110
     * @return mixed
111
     */
112
    public function get(string $path = '', $fallback = '', string $cast = '')
113
    {
114
        $settings = $this->all();
115
        $option = Arr::get($settings, $path, $fallback);
116
        $path = ltrim(Str::removePrefix($path, 'settings'), '.');
117
        if (!empty($path)) {
118
            $hook = 'option/'.str_replace('.', '/', $path);
119
            $option = glsr()->filter($hook, $option, $settings, $path);
120
        }
121
        return Cast::to($cast, $option);
122
    }
123
124
    /**
125
     * This is used when exporting the settings.
126
     */
127
    public function json(): string
128
    {
129
        $all = $this->all();
130
        $all['extra'] = glsr()->filterArray('export/settings/extra', []); // allow addons to export additional data
131
        return (string) wp_json_encode($all, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
132
    }
133 44
134
    public function mergeDefaults(array $defaults): void
135 44
    {
136 44
        $saved = Arr::consolidate($this->wp(static::databaseKey(), []));
137 44
        $defaults = Arr::flatten(Arr::getAs('array', $defaults, 'settings'));
138
        $settings = Arr::flatten(Arr::getAs('array', $saved, 'settings'));
139
        if (empty($defaults) || empty(array_diff_key($defaults, $settings))) {
140
            return;
141
        }
142
        $settings = shortcode_atts($defaults, $settings);
143
        $settings = Arr::unflatten($settings);
144 44
        $settings['strings'] = Arr::consolidate(Arr::get($saved, 'settings.strings'));
145 44
        $saved['settings'] = $settings;
146
        $this->replace($saved);
147
    }
148
149
    public function normalize(array $data = []): array
150
    {
151
        $settings = $this->kses($data);
152
        if (!empty(glsr()->settings)) { // access the property directly to prevent an infinite loop
153
            $defaults = glsr()->defaults(); // @phpstan-ignore-line
154
            $defaults = Arr::flatten($defaults);
155
            $settings = Arr::flatten($settings);
156
            $settings = wp_parse_args($settings, $defaults);
157
            $settings = Arr::unflatten($settings);
158
        }
159
        return $settings;
160 36
    }
161
162 36
    public function previous(): array
163
    {
164
        static::flushSettingsCache();
165 36
        foreach (static::databaseKeys() as $version => $databaseKey) {
166 36
            if ($version === intval(glsr()->version('major'))) {
167
                continue;
168
            }
169 36
            $settings = Arr::consolidate(get_option($databaseKey));
170 36
            if (!empty(array_filter(Arr::getAs('array', $settings, 'settings')))) {
171
                return $settings;
172
            }
173 36
        }
174
        return [];
175 36
    }
176 36
177
    public function replace(array $settings): bool
178
    {
179
        if (empty($settings)) {
180 36
            return false;
181 36
        }
182 36
        $settings = $this->normalize($settings);
183
        if (!update_option(static::databaseKey(), $settings, true)) {
184
            return false;
185
        }
186
        $this->reset();
187
        return true;
188 44
    }
189
190 44
    public function reset(): array
191 44
    {
192 44
        $settings = Arr::consolidate($this->wp(static::databaseKey(), []));
193 44
        if (empty($settings)) {
194
            delete_option(static::databaseKey());
195
            // glsr(Migrate::class)->reset(); // Do this to migrate any previous version settings
196 44
        }
197 44
        $settings = $this->normalize($settings);
198
        glsr()->store('settings', $settings);
199
        return $settings;
200
    }
201
202
    /**
203
     * @param mixed $value
204
     */
205 44
    public function set(string $path, $value = ''): bool
206
    {
207 44
        $settings = $this->all();
208 44
        $settings = Arr::set($settings, $path, $value);
209
        $settings = $this->normalize($settings);
210
        if (!update_option(static::databaseKey(), $settings, true)) {
211 44
            return false;
212
        }
213 44
        glsr()->store('settings', $settings);
214 44
        return true;
215 44
    }
216 44
217
    public function updateVersion(): void
218 44
    {
219 44
        $version = $this->get('version', '0.0.0');
220 44
        if (glsr()->version !== $version) {
221
            $this->set('version', glsr()->version);
222
            $this->set('version_upgraded_from', $version);
223
        }
224
    }
225
226
    /**
227
     * @param mixed $fallback
228
     *
229
     * @return mixed
230
     */
231
    public function wp(string $path, $fallback = '', string $cast = '')
232
    {
233
        $option = get_option($path, $fallback);
234
        return Cast::to($cast, Helper::ifEmpty($option, $fallback, $strict = true));
235
    }
236
237
    public function kses(array $data): array
238
    {
239
        $data = Arr::flatten($data);
240
        array_walk($data, function (&$value) {
241
            if (is_string($value)) {
242
                $value = wp_kses($value, wp_kses_allowed_html('post'));
243
            }
244
        });
245
        $data = Arr::unflatten($data);
246
        return $data;
247
    }
248
249
    /**
250
     * This restores orphaned settings in cases where addons have been deactivated, etc.
251
     */
252
    protected function restoreOrphanedSettings(array $settings, array $saved): array
253
    {
254
        $defaults = glsr()->defaults();
255
        $settings = Arr::set($settings, 'settings.strings', Arr::get($saved, 'settings.strings', []));
256
        foreach (Arr::get($saved, 'settings.addons', []) as $addon => $values) {
257
            if (!isset($defaults['settings']['addons'][$addon])) {
258
                $settings['settings']['addons'][$addon] = $values;
259
            }
260
        }
261
        foreach (Arr::get($saved, 'settings.integrations', []) as $integration => $values) {
262
            if (!isset($defaults['settings']['integrations'][$integration])) {
263
                $settings['settings']['integrations'][$integration] = $values;
264
            }
265
        }
266
        foreach (Arr::get($saved, 'settings.licenses', []) as $addon => $value) {
267
            if (!isset($defaults['settings']['licenses'][$addon])) {
268
                $settings['settings']['licenses'][$addon] = $value;
269
            }
270
        }
271
        return $settings;
272
    }
273
}
274