Passed
Branch master (c471f8)
by Paul
09:53
created

SystemInfo::isWebhostFound()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 5
dl 0
loc 7
ccs 0
cts 7
cp 0
rs 9.2222
c 1
b 1
f 0
cc 6
nc 9
nop 1
crap 42
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\SqlSchema;
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 WP_Debug_Data;
15
16
class SystemInfo
17
{
18
    const PAD = 40;
19
20
    /**
21
     * @return string
22
     */
23
    public function __toString()
24
    {
25
        return $this->get();
26
    }
27
28
    /**
29
     * @return string
30
     */
31
    public function get()
32
    {
33
        $data = $this->data();
34
        $details = [ // order is intentional
35
            'plugin' => $this->getPluginDetails(),
36
            'addon' => $this->getAddonDetails(),
37
            'reviews' => $this->getReviewDetails(),
38
            'browser' => $this->getBrowserDetails(),
39
            'database' => $this->getDatabaseDetails($data),
40
            'server' => $this->getServerDetails($data),
41
            'wordpress' => $this->getWordpressDetails($data),
42
            'mu-plugins' => $this->getMustUsePluginDetails(),
43
            'network-plugins' => $this->getNetworkActivatedPluginDetails(),
44
            'activated-plugins' => $this->getActivePluginDetails(),
45
            'inactive-plugins' => $this->getInactivePluginDetails(),
46
            'setting' => $this->getPluginSettings(),
47
        ];
48
        $systemInfo = array_reduce(array_keys($details), function ($carry, $key) use ($details) {
49
            if (empty(Arr::get($details[$key], 'values'))) {
50
                return $carry;
51
            }
52
            $hook = 'system/'.Str::dashCase($key);
53
            $title = strtoupper(Arr::get($details[$key], 'title'));
54
            $values = glsr()->filterArray($hook, Arr::get($details[$key], 'values'));
55
            return $carry.$this->implode($title, $values);
56
        });
57
        return trim($systemInfo);
58
    }
59
60
    /**
61
     * @return array
62
     */
63
    public function getActivePluginDetails()
64
    {
65
        $plugins = get_plugins();
66
        $active = glsr(OptionManager::class)->getWP('active_plugins', [], 'array');
67
        $inactive = array_diff_key($plugins, array_flip($active));
68
        $activePlugins = $this->normalizePluginList(array_diff_key($plugins, $inactive));
69
        return [
70
            'title' => 'Activated Plugins',
71
            'values' => $activePlugins,
72
        ];
73
    }
74
75
    /**
76
     * @return array
77
     */
78
    public function getAddonDetails()
79
    {
80
        $details = glsr()->filterArray('addon/system-info', []);
81
        ksort($details);
82
        return [
83
            'title' => 'Addon Details',
84
            'values' => $details,
85
        ];
86
    }
87
88
    /**
89
     * @return array
90
     */
91
    public function getBrowserDetails()
92
    {
93
        $browser = new Browser();
94
        $name = esc_attr($browser->getName());
95
        $userAgent = esc_attr($browser->getUserAgent()->getUserAgentString());
96
        $version = esc_attr($browser->getVersion());
97
        return [
98
            'title' => 'Browser Details',
99
            'values' => [
100
                'Browser Name' => sprintf('%s %s', $name, $version),
101
                'Browser UA' => $userAgent,
102
            ],
103
        ];
104
    }
105
106
    /**
107
     * @param array $data
108
     * @return array
109
     */
110
    public function getDatabaseDetails($data)
111
    {
112
        $database = Arr::get($data, 'wp-database');
113
        $engines = glsr(SqlSchema::class)->tableEngines($removeDbPrefix = true);
114
        foreach ($engines as $engine => $tables) {
115
          $engines[$engine] = sprintf('%s (%s)', $engine, implode('|', $tables));
116
        }
117
        return [
118
            'title' => 'Database Details',
119
            'values' => [
120
                'Charset' => Arr::get($database, 'database_charset'),
121
                'Collation' => Arr::get($database, 'database_collate'),
122
                'Extension' => Arr::get($database, 'extension'),
123
                'Table Engines' => implode(', ', $engines),
124
                'Version (client)' => Arr::get($database, 'client_version'),
125
                'Version (server)' => Arr::get($database, 'server_version'),
126
            ],
127
        ];
128
    }
129
130
    /**
131
     * @return array
132
     */
133
    public function getInactivePluginDetails()
134
    {
135
        $active = glsr(OptionManager::class)->getWP('active_plugins', [], 'array');
136
        $inactive = $this->normalizePluginList(array_diff_key(get_plugins(), array_flip($active)));
137
        $networkActivated = $this->getNetworkActivatedPluginDetails();
138
        $networkActivated = Arr::consolidate(Arr::get($networkActivated, 'values'));
139
        $inactivePlugins = Helper::ifTrue(empty($networkActivated), $inactive, array_diff($inactive, $networkActivated));
140
        return [
141
            'title' => 'Inactive Plugins',
142
            'values' => $inactivePlugins,
143
        ];
144
    }
145
146
    /**
147
     * @return array
148
     */
149
    public function getMustUsePluginDetails()
150
    {
151
        $plugins = get_mu_plugins();
152
        $muplugins = Helper::ifTrue(empty($plugins), [], function () use ($plugins) {
153
            return $this->normalizePluginList($plugins);
154
        });
155
        return [
156
            'title' => 'Must-Use Plugins',
157
            'values' => $muplugins,
158
        ];
159
    }
160
161
    /**
162
     * @return array
163
     */
164
    public function getNetworkActivatedPluginDetails()
165
    {
166
        $plugins = Arr::consolidate(get_site_option('active_sitewide_plugins', []));
167
        if (!is_multisite() || empty($plugins)) {
168
            return [];
169
        }
170
        $networkPlugins = $this->normalizePluginList(array_intersect_key(get_plugins(), $plugins));
171
        return [
172
            'title' => 'Network Activated Plugins',
173
            'values' => $networkPlugins,
174
        ];
175
    }
176
177
    /**
178
     * @return array
179
     */
180
    public function getPluginDetails()
181
    {
182
        require_once ABSPATH.'/wp-admin/includes/plugin.php';
183
        return [
184
            'title' => 'Plugin Details',
185
            'values' => [
186
                'Console Level' => glsr(Console::class)->humanLevel(),
187
                'Console Size' => glsr(Console::class)->humanSize(),
188
                'Database Version' => glsr(OptionManager::class)->getWP(glsr()->prefix.'db_version'),
189
                'Last Migration Run' => glsr(Date::class)->localized(glsr(OptionManager::class)->get('last_migration_run'), 'unknown'),
190
                'Network Activated' => Helper::ifTrue(is_plugin_active_for_network(plugin_basename(glsr()->file)), 'Yes', 'No'),
191
                'Version' => sprintf('%s (%s)', glsr()->version, glsr(OptionManager::class)->get('version_upgraded_from')),
192
            ],
193
        ];
194
    }
195
196
    /**
197
     * @return array
198
     */
199
    public function getPluginSettings()
200
    {
201
        $settings = glsr(OptionManager::class)->getArray('settings');
202
        $settings = Arr::flatten($settings, true);
203
        $settings = $this->purgeSensitiveData($settings);
204
        ksort($settings);
205
        $details = [];
206
        foreach ($settings as $key => $value) {
207
            if (Str::startsWith('strings', $key) && Str::endsWith('id', $key)) {
208
                continue;
209
            }
210
            $value = htmlspecialchars(trim(preg_replace('/\s\s+/u', '\\n', $value)), ENT_QUOTES, 'UTF-8');
211
            $details[$key] = $value;
212
        }
213
        return [
214
            'title' => 'Plugin Settings',
215
            'values' => $details,
216
        ];
217
    }
218
219
    /**
220
     * @return array
221
     */
222
    public function getReviewDetails()
223
    {
224
        $ratings = $this->ratingCounts();
225
        $reviews = $this->reviewCounts();
226
        $values = array_merge($ratings, $reviews);
227
        ksort($values);
228
        return [
229
            'title' => 'Review Details',
230
            'values' => $values,
231
        ];
232
    }
233
234
    /**
235
     * @param array $data
236
     * @return array
237
     */
238
    public function getServerDetails($data)
239
    {
240
        $media = Arr::get($data, 'wp-media');
241
        $server = Arr::get($data, 'wp-server');
242
        return [
243
            'title' => 'Server Details',
244
            'values' => [
245
                'cURL Version' => Arr::get($server, 'curl_version'),
246
                'Display Errors' => Helper::ifEmpty($this->getIni('display_errors'), 'No'),
247
                'File Uploads' => Arr::get($media, 'file_uploads'),
248
                'GD version' => Arr::get($media, 'gd_version'),
249
                'Ghostscript version' => Arr::get($media, 'ghostscript_version'),
250
                'Host Name' => $this->getHostName(),
251
                'ImageMagick version' => Arr::get($media, 'imagemagick_version'),
252
                'Intl' => Helper::ifEmpty(phpversion('intl'), 'No'),
253
                'IPv6' => var_export(defined('AF_INET6'), true),
254
                'Max Effective File Size' => Arr::get($media, 'max_effective_size'),
255
                'Max Execution Time' => Arr::get($server, 'time_limit'),
256
                'Max File Uploads' => Arr::get($media, 'max_file_uploads'),
257
                'Max Input Time' => Arr::get($server, 'max_input_time'),
258
                'Max Input Variables' => Arr::get($server, 'max_input_variables'),
259
                'Memory Limit' => Arr::get($server, 'memory_limit'),
260
                'Multibyte' => Helper::ifEmpty(phpversion('mbstring'), 'No'),
261
                'Permalinks Supported' => Arr::get($server, 'pretty_permalinks'),
262
                'PHP Version' => Arr::get($server, 'php_version'),
263
                'Post Max Size' => Arr::get($server, 'php_post_max_size'),
264
                'SAPI' => Arr::get($server, 'php_sapi'),
265
                'Sendmail' => $this->getIni('sendmail_path'),
266
                'Server Architecture' => Arr::get($server, 'server_architecture'),
267
                'Server Software' => Arr::get($server, 'httpd_software'),
268
                'SUHOSIN Installed' => Arr::get($server, 'suhosin'),
269
                'Upload Max Filesize' => Arr::get($server, 'upload_max_filesize'),
270
            ],
271
        ];
272
    }
273
274
    /**
275
     * @param array $data
276
     * @return array
277
     */
278
    public function getWordpressDetails($data)
279
    {
280
        $constants = Arr::get($data, 'wp-constants');
281
        $wordpress = Arr::get($data, 'wp-core');
282
        return [
283
            'title' => 'WordPress Configuration',
284
            'values' => [
285
                'Email Domain' => substr(strrchr(glsr(OptionManager::class)->getWP('admin_email'), '@'), 1),
286
                'Environment' => Arr::get($wordpress, 'environment_type'),
287
                'Hidden From Search Engines' => Arr::get($wordpress, 'blog_public'),
288
                'Home URL' => Arr::get($wordpress, 'home_url'),
289
                'HTTPS' => Arr::get($wordpress, 'https_status'),
290
                'Language (site)' => Arr::get($wordpress, 'site_language'),
291
                'Language (user)' => Arr::get($wordpress, 'user_language'),
292
                'Multisite' => Arr::get($wordpress, 'multisite'),
293
                'Page For Posts ID' => glsr(OptionManager::class)->getWP('page_for_posts'),
294
                'Page On Front ID' => glsr(OptionManager::class)->getWP('page_on_front'),
295
                'Permalink Structure' => Arr::get($wordpress, 'permalink'),
296
                'Post Stati' => implode(', ', get_post_stati()),
297
                'Remote Post' => glsr(Cache::class)->getRemotePostTest(),
298
                'SCRIPT_DEBUG' => Arr::get($constants, 'SCRIPT_DEBUG'),
299
                'Show On Front' => glsr(OptionManager::class)->getWP('show_on_front'),
300
                'Site URL' => Arr::get($wordpress, 'site_url'),
301
                'Theme (active)' => sprintf('%s v%s', Arr::get($data, 'wp-active-theme.name'), Arr::get($data, 'wp-active-theme.version')),
302
                'Theme (parent)' => Arr::get($data, 'wp-parent-theme.name', 'No'),
303
                'Timezone' => Arr::get($wordpress, 'timezone'),
304
                'User Count' => Arr::get($wordpress, 'user_count'),
305
                'Version' => Arr::get($wordpress, 'version'),
306
                'WP_CACHE' => Arr::get($constants, 'WP_CACHE'),
307
                'WP_DEBUG' => Arr::get($constants, 'WP_DEBUG'),
308
                'WP_DEBUG_DISPLAY' => Arr::get($constants, 'WP_DEBUG_DISPLAY'),
309
                'WP_DEBUG_LOG' => Arr::get($constants, 'WP_DEBUG_LOG'),
310
                'WP_MAX_MEMORY_LIMIT' => Arr::get($constants, 'WP_MAX_MEMORY_LIMIT'),
311
            ],
312
        ];
313
    }
314
315
    /**
316
     * @return array
317
     */
318
    public function data()
319
    {
320
        $data = glsr(Cache::class)->getSystemInfo();
321
        array_walk($data, function (&$section) {
322
            $fields = Arr::consolidate(Arr::get($section, 'fields'));
323
            array_walk($fields, function (&$values) {
324
                $values = Arr::get($values, 'value');
325
            });
326
            $section = $fields;
327
        });
328
        return $data;
329
    }
330
331
    /**
332
     * @return string
333
     */
334
    protected function getHostName()
335
    {
336
        return sprintf('%s (%s)', $this->detectWebhostProvider(), Helper::getIpAddress());
337
    }
338
339
    /**
340
     * @param string $name
341
     * @param string $disabledValue
342
     * @return string
343
     */
344
    protected function getIni($name, $disabledValue = 'ini_get() is disabled.')
345
    {
346
        return Helper::ifTrue(!function_exists('ini_get'), $disabledValue, function () use ($name) {
347
            return ini_get($name);
348
        });
349
    }
350
351
    /**
352
     * @param string $title
353
     * @return string
354
     */
355
    protected function implode($title, array $details)
356
    {
357
        $strings = ['['.$title.']'];
358
        $padding = max(array_map('strlen', array_keys($details)));
359
        $padding = max([$padding, static::PAD]);
360
        foreach ($details as $key => $value) {
361
            $pad = $padding - (mb_strlen($key, 'UTF-8') - strlen($key)); // handle unicode characters
362
            $strings[] = is_string($key)
363
                ? sprintf('%s : %s', str_pad($key, $pad, '.'), $value)
364
                : ' - '.$value;
365
        }
366
        return implode(PHP_EOL, $strings).PHP_EOL.PHP_EOL;
367
    }
368
369
    /**
370
     * @return string
371
     */
372
    protected function detectWebhostProvider()
373
    {
374
        $checks = [
375
            '.accountservergroup.com' => 'Site5',
376
            '.gridserver.com' => 'MediaTemple Grid',
377
            '.inmotionhosting.com' => 'InMotion Hosting',
378
            '.ovh.net' => 'OVH',
379
            '.pair.com' => 'pair Networks',
380
            '.stabletransit.com' => 'Rackspace Cloud',
381
            '.stratoserver.net' => 'STRATO',
382
            '.sysfix.eu' => 'SysFix.eu Power Hosting',
383
            'bluehost.com' => 'Bluehost',
384
            'DH_USER' => 'DreamHost',
385
            'Flywheel' => 'Flywheel',
386
            'ipagemysql.com' => 'iPage',
387
            'ipowermysql.com' => 'IPower',
388
            'localhost:/tmp/mysql5.sock' => 'ICDSoft',
389
            'mysqlv5' => 'NetworkSolutions',
390
            'PAGELYBIN' => 'Pagely',
391
            'secureserver.net' => 'GoDaddy',
392
            'WPE_APIKEY' => 'WP Engine',
393
        ];
394
        foreach ($checks as $key => $value) {
395
            if (!$this->isWebhostFound($key)) {
396
                continue;
397
            }
398
            return $value;
399
        }
400
        return implode(',', array_filter([DB_HOST, filter_input(INPUT_SERVER, 'SERVER_NAME')]));
401
    }
402
403
    /**
404
     * @param string $key
405
     * @return bool
406
     */
407
    protected function isWebhostFound($key)
408
    {
409
        return defined($key)
410
            || filter_input(INPUT_SERVER, $key)
411
            || Str::contains($key, filter_input(INPUT_SERVER, 'SERVER_NAME'))
412
            || Str::contains($key, DB_HOST)
413
            || (function_exists('php_uname') && Str::contains($key, php_uname()));
414
    }
415
416
    /**
417
     * @return array
418
     */
419
    protected function normalizePluginList(array $plugins)
420
    {
421
        $plugins = array_map(function ($plugin) {
422
            return sprintf('%s v%s', Arr::get($plugin, 'Name'), Arr::get($plugin, 'Version'));
423
        }, $plugins);
424
        natcasesort($plugins);
425
        return array_flip($plugins);
426
    }
427
428
    /**
429
     * @return array
430
     */
431
    protected function purgeSensitiveData(array $settings)
432
    {
433
        $keys = glsr()->filterArray('addon/system-info/purge', [
434
            'licenses.' => 8,
435
            'submissions.recaptcha.key' => 0,
436
            'submissions.recaptcha.secret' => 0,
437
        ]);
438
        array_walk($settings, function (&$value, $setting) use ($keys) {
439
            foreach ($keys as $key => $preserve) {
440
                if (!is_string($key)) { // @compat for older addons
441
                    $key = $preserve;
442
                    $preserve = 0;
443
                }
444
                if (Str::startsWith($key, $setting) && !empty($value)) {
445
                    $preserve = Cast::toInt($preserve);
446
                    $value = substr($value, -$preserve, $preserve);
447
                    $value = str_pad($value, 13, '*', STR_PAD_LEFT);
448
                    break;
449
                }
450
            }
451
        });
452
        return $settings;
453
    }
454
455
    /**
456
     * @return array
457
     */
458
    protected function ratingCounts()
459
    {
460
        $ratings = glsr(Query::class)->ratings();
461
        $results = [];
462
        foreach ($ratings as $type => $counts) {
463
            if (is_array($counts)) {
464
                $label = sprintf('Type: %s', $type);
465
                $results[$label] = array_sum($counts).' ('.implode(', ', $counts).')';
466
                continue;
467
            }
468
            glsr_log()->error('$ratings is not an array, possibly due to incorrectly imported reviews.')
469
                ->debug($counts)
470
                ->debug($ratings);
471
        }
472
        if (empty($results)) {
473
            return ['Type: local' => 'No reviews'];
474
        }
475
        return $results;
476
    }
477
478
    /**
479
     * @return array
480
     */
481
    protected function reviewCounts()
482
    {
483
        $reviews = array_filter((array) wp_count_posts(glsr()->post_type));
484
        $counts = array_sum($reviews);
485
        foreach ($reviews as $status => &$num) {
486
            $num = sprintf('%s: %d', $status, $num);
487
        }
488
        $results = $counts.' ('.implode(', ', $reviews).')';
489
        return ['Reviews' => $results];
490
    }
491
}
492