SystemInfo   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 60
eloc 275
c 3
b 1
f 1
dl 0
loc 433
ccs 0
cts 322
cp 0
rs 3.6

27 Methods

Rating   Name   Duplication   Size   Complexity  
A group() 0 3 1
A __construct() 0 3 1
A getPlugin() 0 16 1
A __toString() 0 3 1
A hostname() 0 30 3
A getAddon() 0 12 3
A getSettings() 0 17 4
A implode() 0 12 2
A plugins() 0 6 1
A getDatabase() 0 24 3
A getDropIns() 0 5 1
A ini() 0 6 2
A getActivePlugins() 0 5 1
A getMuPlugins() 0 5 1
A getInactivePlugins() 0 5 1
A getBrowser() 0 11 1
A get() 0 31 3
A getWordpress() 0 31 1
A getReviews() 0 7 1
A data() 0 13 2
B isWebhost() 0 8 8
A reviewCounts() 0 12 3
A ratingCounts() 0 17 4
A value() 0 3 1
A getActionScheduler() 0 28 4
A purgeSensitiveData() 0 14 5
A getServer() 0 30 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use GeminiLabs\Sinergi\BrowserDetector\Browser;
6
use GeminiLabs\SiteReviews\Database\Cache;
7
use GeminiLabs\SiteReviews\Database\OptionManager;
8
use GeminiLabs\SiteReviews\Database\Query;
9
use GeminiLabs\SiteReviews\Database\Tables;
10
use GeminiLabs\SiteReviews\Helper;
11
use GeminiLabs\SiteReviews\Helpers\Arr;
12
use GeminiLabs\SiteReviews\Helpers\Cast;
13
use GeminiLabs\SiteReviews\Helpers\Str;
14
use GeminiLabs\SiteReviews\Modules\Migrate;
15
16
class SystemInfo
17
{
18
    public const PAD = 40;
19
20
    protected $data;
21
22
    public function __construct()
23
    {
24
        require_once ABSPATH.'wp-admin/includes/plugin.php';
25
    }
26
27
    public function __toString()
28
    {
29
        return $this->get();
30
    }
31
32
    public function get(): string
33
    {
34
        $keys = [ // order is intentional
35
            'plugin',
36
            'addon',
37
            'reviews',
38
            'browser',
39
            'database',
40
            'action-scheduler',
41
            'server',
42
            'wordpress',
43
            'drop-ins',
44
            'mu-plugins',
45
            'active-plugins',
46
            'inactive-plugins',
47
            'settings',
48
        ];
49
        return trim(array_reduce($keys, function ($carry, $key) {
50
            $method = Helper::buildMethodName('get', $key);
51
            if (!method_exists($this, $method)) {
52
                return $carry;
53
            }
54
            $details = call_user_func([$this, $method]);
55
            if (empty(Arr::get($details, 'values'))) {
56
                return $carry;
57
            }
58
            $section = Str::dashCase($key);
59
            $title = strtoupper(Arr::get($details, 'title'));
60
            $values = Arr::get($details, 'values');
61
            $values = glsr()->filterArray("system-info/section/{$section}", $values, $this->data);
62
            return $carry.$this->implode($title, $values);
63
        }));
64
    }
65
66
    public function getActionScheduler(): array
67
    {
68
        $counts = glsr(Queue::class)->actionCounts();
69
        $counts = shortcode_atts(array_fill_keys(['complete', 'pending', 'failed'], []), $counts);
70
        $values = [];
71
        foreach ($counts as $key => $value) {
72
            $label = sprintf('Actions (%s)', $key);
73
            $value = wp_parse_args($value, array_fill_keys(['count', 'latest', 'oldest'], 0));
74
            if ($value['count'] > 1) {
75
                $values[$label] = sprintf('%s (latest: %s, oldest: %s)',
76
                    $value['count'],
77
                    $value['latest'],
78
                    $value['oldest']
79
                );
80
            } elseif (!empty($value['latest'])) {
81
                $values[$label] = sprintf('%s (latest: %s)',
82
                    $value['count'],
83
                    $value['latest']
84
                );
85
            } else {
86
                $values[$label] = $value['count'];
87
            }
88
        }
89
        $values['Data Store'] = get_class(\ActionScheduler_Store::instance());
90
        $values['Version'] = \ActionScheduler_Versions::instance()->latest_version();
91
        return [
92
            'title' => 'Action Scheduler',
93
            'values' => $values,
94
        ];
95
    }
96
97
    public function getActivePlugins(): array
98
    {
99
        return [
100
            'title' => 'Active Plugins',
101
            'values' => $this->plugins($this->group('wp-plugins-active')),
102
        ];
103
    }
104
105
    public function getAddon(): array
106
    {
107
        $details = [];
108
        foreach (glsr()->retrieveAs('array', 'addons') as $id => $version) {
109
            if ($addon = glsr($id)) {
110
                $details[$addon->name] = $addon->version;
111
            }
112
        }
113
        ksort($details);
114
        return [
115
            'title' => 'Addon Details',
116
            'values' => $details,
117
        ];
118
    }
119
120
    public function getBrowser(): array
121
    {
122
        $browser = new Browser();
123
        $name = esc_attr($browser->getName());
124
        $userAgent = esc_attr($browser->getUserAgent()->getUserAgentString());
125
        $version = esc_attr($browser->getVersion());
126
        return [
127
            'title' => 'Browser Details',
128
            'values' => [
129
                'Browser Name' => sprintf('%s %s', $name, $version),
130
                'Browser UA' => $userAgent,
131
            ],
132
        ];
133
    }
134
135
    public function getDatabase(): array
136
    {
137
        if (glsr(Tables::class)->isSqlite()) {
138
            $values = [
139
                'Database Engine' => $this->value('wp-database.db_engine'),
140
                'Database Version' => $this->value('wp-database.database_version'),
141
            ];
142
        } else {
143
            $engines = glsr(Tables::class)->tableEngines($removePrefix = true);
144
            foreach ($engines as $engine => $tables) {
145
                $engines[$engine] = sprintf('%s (%s)', $engine, implode('|', $tables));
146
            }
147
            $values = [
148
                'Charset' => $this->value('wp-database.database_charset'),
149
                'Collation' => $this->value('wp-database.database_collate'),
150
                'Extension' => $this->value('wp-database.extension'),
151
                'Table Engines' => implode(', ', $engines),
152
                'Version (client)' => $this->value('wp-database.client_version'),
153
                'Version (server)' => $this->value('wp-database.server_version'),
154
            ];
155
        }
156
        return [
157
            'title' => 'Database Details',
158
            'values' => $values,
159
        ];
160
    }
161
162
    public function getDropIns(): array
163
    {
164
        return [
165
            'title' => 'Drop-ins',
166
            'values' => $this->group('wp-dropins'),
167
        ];
168
    }
169
170
    public function getInactivePlugins(): array
171
    {
172
        return [
173
            'title' => 'Inactive Plugins',
174
            'values' => $this->plugins($this->group('wp-plugins-inactive')),
175
        ];
176
    }
177
178
    public function getMuPlugins()
179
    {
180
        return [
181
            'title' => 'Must-Use Plugins',
182
            'values' => $this->plugins($this->group('wp-mu-plugins')),
183
        ];
184
    }
185
186
    public function getPlugin(): array
187
    {
188
        $merged = array_keys(array_filter([
189
            'css' => glsr()->filterBool('optimize/css', false),
190
            'js' => glsr()->filterBool('optimize/js', false),
191
        ]));
192
        return [
193
            'title' => 'Plugin Details',
194
            'values' => [
195
                'Console Level' => glsr(Console::class)->humanLevel(),
196
                'Console Size' => glsr(Console::class)->humanSize(),
197
                'Database Version' => (string) get_option(glsr()->prefix.'db_version'),
198
                'Last Migration Run' => glsr(Date::class)->localized(glsr(Migrate::class)->lastRun(), 'unknown'),
199
                'Merged Assets' => implode('/', Helper::ifEmpty($merged, ['No'])),
200
                'Network Activated' => Helper::ifTrue(is_plugin_active_for_network(glsr()->basename), 'Yes', 'No'),
201
                'Version' => sprintf('%s (%s)', glsr()->version, glsr(OptionManager::class)->get('version_upgraded_from')),
202
            ],
203
        ];
204
    }
205
206
    public function getReviews(): array
207
    {
208
        $values = array_merge($this->ratingCounts(), $this->reviewCounts());
209
        ksort($values);
210
        return [
211
            'title' => 'Review Details',
212
            'values' => $values,
213
        ];
214
    }
215
216
    public function getServer(): array
217
    {
218
        return [
219
            'title' => 'Server Details',
220
            'values' => [
221
                'cURL Version' => $this->value('wp-server.curl_version'),
222
                'Display Errors' => $this->ini('display_errors', 'No'),
223
                'File Uploads' => $this->value('wp-media.file_uploads'),
224
                'GD version' => $this->value('wp-media.gd_version'),
225
                'Ghostscript Version' => $this->value('wp-media.ghostscript_version'),
226
                'Host Name' => $this->hostname(),
227
                'ImageMagick Version' => $this->value('wp-media.imagemagick_version'),
228
                'Intl' => Helper::ifEmpty(phpversion('intl'), 'No'),
229
                'IPv6' => var_export(defined('AF_INET6'), true),
230
                'Max Effective File Size' => $this->value('wp-media.max_effective_size'),
231
                'Max Execution Time' => $this->value('wp-server.time_limit'),
232
                'Max File Uploads' => $this->value('wp-media.max_file_uploads'),
233
                'Max Input Time' => $this->value('wp-server.max_input_time'),
234
                'Max Input Variables' => $this->value('wp-server.max_input_variables'),
235
                'Memory Limit' => $this->value('wp-server.memory_limit'),
236
                'Multibyte' => Helper::ifEmpty(phpversion('mbstring'), 'No'),
237
                'Permalinks Supported' => $this->value('wp-server.pretty_permalinks'),
238
                'PHP Version' => $this->value('wp-server.php_version'),
239
                'Post Max Size' => $this->value('wp-server.php_post_max_size'),
240
                'SAPI' => $this->value('wp-server.php_sapi'),
241
                'Sendmail' => $this->ini('sendmail_path'),
242
                'Server Architecture' => $this->value('wp-server.server_architecture'),
243
                'Server Software' => $this->value('wp-server.httpd_software'),
244
                'SUHOSIN Installed' => $this->value('wp-server.suhosin'),
245
                'Upload Max Filesize' => $this->value('wp-server.upload_max_filesize'),
246
            ],
247
        ];
248
    }
249
250
    public function getSettings(): array
251
    {
252
        $settings = glsr(OptionManager::class)->getArray('settings');
253
        $settings = Arr::flatten($settings, true);
254
        $settings = $this->purgeSensitiveData($settings);
255
        ksort($settings);
256
        $details = [];
257
        foreach ($settings as $key => $value) {
258
            if (str_starts_with($key, 'strings') && str_ends_with($key, 'id')) {
259
                continue;
260
            }
261
            $value = htmlspecialchars(trim(preg_replace('/\s\s+/u', '\\n', $value)), ENT_QUOTES, 'UTF-8');
262
            $details[$key] = $value;
263
        }
264
        return [
265
            'title' => 'Plugin Settings',
266
            'values' => $details,
267
        ];
268
    }
269
270
    public function getWordpress(): array
271
    {
272
        return [
273
            'title' => 'WordPress Configuration',
274
            'values' => [
275
                'Email Domain' => substr(strrchr((string) get_option('admin_email'), '@'), 1),
276
                'Environment' => $this->value('wp-core.environment_type'),
277
                'Hidden From Search Engines' => $this->value('wp-core.blog_public'),
278
                'Home URL' => $this->value('wp-core.home_url'),
279
                'HTTPS' => $this->value('wp-core.https_status'),
280
                'Language (site)' => $this->value('wp-core.site_language'),
281
                'Language (user)' => $this->value('wp-core.user_language'),
282
                'Multisite' => $this->value('wp-core.multisite'),
283
                'Page For Posts ID' => (string) get_option('page_for_posts'),
284
                'Page On Front ID' => (string) get_option('page_on_front'),
285
                'Permalink Structure' => $this->value('wp-core.permalink'),
286
                'Post Stati' => implode(', ', get_post_stati()), // @phpstan-ignore-line
287
                'Remote Post' => glsr(Cache::class)->getRemotePostTest(),
288
                'SCRIPT_DEBUG' => $this->value('wp-constants.SCRIPT_DEBUG'),
289
                'Show On Front' => (string) get_option('show_on_front'),
290
                'Site URL' => $this->value('wp-core.site_url'),
291
                'Theme (active)' => sprintf('%s v%s by %s', $this->value('wp-active-theme.name'), $this->value('wp-active-theme.version'), $this->value('wp-active-theme.author')),
292
                'Theme (parent)' => $this->value('wp-parent-theme.name', 'No'),
293
                'Timezone' => $this->value('wp-core.timezone'),
294
                'User Count' => $this->value('wp-core.user_count'),
295
                'Version' => $this->value('wp-core.version'),
296
                'WP_CACHE' => $this->value('wp-constants.WP_CACHE'),
297
                'WP_DEBUG' => $this->value('wp-constants.WP_DEBUG'),
298
                'WP_DEBUG_DISPLAY' => $this->value('wp-constants.WP_DEBUG_DISPLAY'),
299
                'WP_DEBUG_LOG' => $this->value('wp-constants.WP_DEBUG_LOG'),
300
                'WP_MAX_MEMORY_LIMIT' => $this->value('wp-constants.WP_MAX_MEMORY_LIMIT'),
301
            ],
302
        ];
303
    }
304
305
    protected function data(): array
306
    {
307
        if (empty($this->data)) {
308
            $this->data = glsr(Cache::class)->getSystemInfo();
309
            array_walk($this->data, function (&$section) {
310
                $fields = Arr::consolidate(Arr::get($section, 'fields'));
311
                array_walk($fields, function (&$values) {
312
                    $values = Arr::get($values, 'value');
313
                });
314
                $section = $fields;
315
            });
316
        }
317
        return $this->data;
318
    }
319
320
    protected function group(string $key): array
321
    {
322
        return Arr::getAs('array', $this->data(), $key);
323
    }
324
325
    protected function hostname(): string
326
    {
327
        $checks = [
328
            '.accountservergroup.com' => 'Site5',
329
            '.gridserver.com' => 'MediaTemple Grid',
330
            '.inmotionhosting.com' => 'InMotion Hosting',
331
            '.ovh.net' => 'OVH',
332
            '.pair.com' => 'pair Networks',
333
            '.stabletransit.com' => 'Rackspace Cloud',
334
            '.stratoserver.net' => 'STRATO',
335
            '.sysfix.eu' => 'SysFix.eu Power Hosting',
336
            'bluehost.com' => 'Bluehost',
337
            'DH_USER' => 'DreamHost',
338
            'Flywheel' => 'Flywheel',
339
            'ipagemysql.com' => 'iPage',
340
            'ipowermysql.com' => 'IPower',
341
            'localhost:/tmp/mysql5.sock' => 'ICDSoft',
342
            'mysqlv5' => 'NetworkSolutions',
343
            'PAGELYBIN' => 'Pagely',
344
            'secureserver.net' => 'GoDaddy',
345
            'WPE_APIKEY' => 'WP Engine',
346
        ];
347
        $webhost = implode(',', array_filter([DB_HOST, filter_input(INPUT_SERVER, 'SERVER_NAME')]));
348
        foreach ($checks as $key => $value) {
349
            if ($this->isWebhost($key)) {
350
                $webhost = $value;
351
                break;
352
            }
353
        }
354
        return sprintf('%s (%s)', $webhost, Helper::getIpAddress());
355
    }
356
357
    protected function implode(string $title, array $details): string
358
    {
359
        $strings = ["[{$title}]"];
360
        $padding = max(static::PAD, ...array_map(function ($key) {
361
            return mb_strlen(html_entity_decode($key, ENT_HTML5), 'UTF-8');
362
        }, array_keys($details)));
363
        foreach ($details as $key => $value) {
364
            $key = html_entity_decode((string) $key, ENT_HTML5);
365
            $pad = $padding - (mb_strlen($key, 'UTF-8') - strlen($key)); // handle unicode character lengths
366
            $strings[] = sprintf('%s : %s', str_pad($key, $pad, '.'), $value);
367
        }
368
        return implode(PHP_EOL, $strings).PHP_EOL.PHP_EOL;
369
    }
370
371
    protected function ini(string $name, string $fallback = ''): string
372
    {
373
        if (function_exists('ini_get')) {
374
            return Helper::ifEmpty(ini_get($name), $fallback);
375
        }
376
        return 'ini_get() is disabled.';
377
    }
378
379
    protected function isWebhost(string $key): bool
380
    {
381
        return defined($key)
382
            || filter_input(INPUT_SERVER, $key)
383
            || str_contains((string) filter_input(INPUT_SERVER, 'SERVER_NAME'), $key)
384
            || str_contains(DB_HOST, $key)
385
            || (function_exists('php_uname') && str_contains(php_uname(), $key))
386
            || ('WPE_APIKEY' === $key && function_exists('is_wpe')); // WP Engine
387
    }
388
389
    protected function plugins(array $plugins): array
390
    {
391
        return array_map(function ($value) {
392
            $patterns = ['/^(Version )/', '/( \| Auto-updates (en|dis)abled)$/'];
393
            return preg_replace($patterns, ['v', ''], $value);
394
        }, $plugins);
395
    }
396
397
    protected function purgeSensitiveData(array $settings): array
398
    {
399
        $config = glsr()->settings();
400
        $config = array_filter($config, function ($field, $key) {
401
            return str_starts_with($key, 'settings.licenses.') || str_ends_with($key, 'api_key') || 'secret' === ($field['type'] ?? '');
402
        }, ARRAY_FILTER_USE_BOTH);
403
        $keys = array_keys($config);
404
        $keys = array_map(fn ($key) => Str::removePrefix($key, 'settings.'), $keys);
405
        foreach ($settings as $key => &$value) {
406
            if (in_array($key, $keys)) {
407
                $value = Str::mask($value, 0, 8, 24);
408
            }
409
        }
410
        return $settings;
411
    }
412
413
    protected function ratingCounts(): array
414
    {
415
        $ratings = glsr(Query::class)->ratings();
416
        $results = [];
417
        foreach ($ratings as $type => $counts) {
418
            if (is_array($counts)) {
419
                $label = sprintf('Type: %s', $type);
420
                $results[$label] = array_sum($counts).' ('.implode(', ', $counts).')';
421
                continue;
422
            }
423
            glsr_log()->error('$ratings is not an array, possibly due to incorrectly imported reviews.')
424
                ->debug(compact('counts', 'ratings'));
425
        }
426
        if (empty($results)) {
427
            return ['Type: local' => 'No reviews'];
428
        }
429
        return $results;
430
    }
431
432
    protected function reviewCounts(): array
433
    {
434
        $reviews = array_filter((array) wp_count_posts(glsr()->post_type));
435
        $results = array_sum($reviews);
436
        if (0 < $results) {
437
            foreach ($reviews as $status => &$num) {
438
                $num = sprintf('%s: %d', $status, $num);
439
            }
440
            $details = implode(', ', $reviews);
441
            $results = "{$results} ({$details})";
442
        }
443
        return ['Reviews' => $results];
444
    }
445
446
    protected function value(string $path = '', string $fallback = ''): string
447
    {
448
        return Arr::getAs('string', $this->data(), $path, $fallback);
449
    }
450
}
451