PluginProbe
Server Info – System Health & Diagnostics Suite / 1.1.0
Server Info – System Health & Diagnostics Suite v1.1.0
1.1.0 trunk 0.01 1.0.0
server-info / server-info.php

server-info.php in Server Info – System Health & Diagnostics Suite 1.1.0, at server-info.php

2,956 lines 105.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Server Info - System Health & Diagnostics Suite
4 * Plugin URI: https://wordpress.org/plugins/server-info/
5 * Description: The ultimate dashboard to monitor server configuration, database health, caching performance, and critical WordPress diagnostics in real-time.
6 * Version: 1.1.0
7 * Requires at least: 5.5
8 * Requires PHP: 7.3
9 * Author: Usman Ali Qureshi
10 * Author URI: https://usmanaliqureshi.com/
11 * License: GPLv2 or later
12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
13 * Text Domain: server-info
14 * Domain Path: /languages/
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 if ( ! function_exists( 'server_info_fs' ) ) {
22 /**
23 * Freemius SDK helper.
24 *
25 * @return Freemius
26 */
27 function server_info_fs() {
28 global $server_info_fs;
29
30 if ( ! isset( $server_info_fs ) ) {
31 require_once __DIR__ . '/vendor/freemius/start.php';
32
33 $server_info_fs = fs_dynamic_init( array(
34 'id' => '2860',
35 'slug' => 'server-info',
36 'type' => 'plugin',
37 'public_key' => 'pk_6e7a210fbe9898524cf4df3c6d6fb',
38 'is_premium' => false,
39 'has_addons' => false,
40 'has_paid_plans' => true,
41 'is_org_compliant' => true,
42 'menu' => array(
43 'slug' => 'server_info_display',
44 'account' => false,
45 'contact' => false,
46 'support' => false,
47 'pricing' => false,
48 'parent' => array(
49 'slug' => 'options-general.php',
50 ),
51 ),
52 ) );
53 }
54
55 return $server_info_fs;
56 }
57
58 server_info_fs();
59 server_info_fs()->add_filter( 'is_pricing_page_visible', '__return_false' );
60 do_action( 'server_info_fs_loaded' );
61 }
62
63 // Set the plugin version.
64 define( 'SERVER_INFO_PLUGIN_VERSION', '1.1.0' );
65
66 // Set the plugin file.
67 define( 'SERVER_INFO_PLUGIN_FILE', __FILE__ );
68
69 // Set the absolute path for the plugin.
70 define( 'SERVER_INFO_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
71
72 // Set the plugin URL root.
73 define( 'SERVER_INFO_PLUGIN_URL', plugins_url( '/', __FILE__ ) );
74
75 // Set the plugin option key.
76 define( 'SERVER_INFO_OPTION_NAME', 'server_info_options' );
77
78 // Freemius product values for the optional supporter checkout.
79 define( 'SERVER_INFO_FREEMIUS_PRODUCT_ID', '2860' );
80 define( 'SERVER_INFO_FREEMIUS_SUPPORTER_PLAN_ID', '61241' );
81 define( 'SERVER_INFO_FREEMIUS_BACKER_PLAN_ID', '61242' );
82 define( 'SERVER_INFO_FREEMIUS_SPONSOR_PLAN_ID', '61243' );
83 define( 'SERVER_INFO_FREEMIUS_AGENCY_SPONSOR_PLAN_ID', '61244' );
84 define( 'SERVER_INFO_FREEMIUS_PUBLIC_KEY', 'pk_6e7a210fbe9898524cf4df3c6d6fb' );
85
86 /**
87 * Class Server_Info
88 *
89 * Main plugin class that handles the collection and display of server information.
90 *
91 * @package Server_Info
92 * @since 0.0.1
93 */
94 class Server_Info {
95
96 /**
97 * Singleton instance static property.
98 *
99 * @var Server_Info|bool
100 * @since 0.0.1
101 */
102 static $instance = false;
103
104 /**
105 * Retrieves the singleton instance of the class.
106 *
107 * @since 0.0.1
108 * @access public
109 *
110 * @return Server_Info The singleton instance.
111 */
112 public static function getInstance() {
113 if ( ! self::$instance ) {
114 self::$instance = new self;
115 }
116
117 return self::$instance;
118 }
119
120 /**
121 * Detect the current environment.
122 *
123 * @return string
124 */
125 public static function get_environment_type() {
126 $env = 'Production';
127 if ( function_exists( 'wp_get_environment_type' ) ) {
128 $env = ucfirst( wp_get_environment_type() );
129 }
130
131 $host = self::get_server_value( 'HTTP_HOST' );
132 if ( strpos( $host, '.local' ) !== false || strpos( $host, '.test' ) !== false || strpos( $host, 'localhost' ) !== false ) {
133 $env = 'Local';
134 } elseif ( strpos( $host, 'staging.' ) !== false || strpos( $host, 'dev.' ) !== false ) {
135 $env = 'Staging';
136 }
137
138 return $env;
139 }
140
141 /**
142 * Returns a sanitized value from the web server environment.
143 *
144 * @param string $key Server variable key.
145 * @param string $default Fallback value.
146 * @return string
147 */
148 public static function get_server_value( $key, $default = '' ) {
149 if ( ! isset( $_SERVER[ $key ] ) ) {
150 return $default;
151 }
152
153 $value = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
154
155 return '' !== $value ? $value : $default;
156 }
157
158 /**
159 * Plugin constructor.
160 *
161 * @since 0.0.1
162 * @access private
163 */
164 private function __construct() {
165 $this->init();
166 }
167
168 /**
169 * Initializes plugin hooks and actions.
170 *
171 * @since 0.0.1
172 * @access public
173 *
174 * @return void
175 */
176 public function init() {
177 add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ) );
178 add_action( 'admin_menu', array( $this, 'add_plugin_menu' ) );
179 add_action( 'admin_init', array( $this, 'register_settings' ) );
180 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
181 add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_hud' ), 999 );
182
183 // Footer HUD
184 add_filter( 'admin_footer_text', array( $this, 'add_admin_footer_text' ), 999 );
185 add_filter( 'update_footer', array( $this, 'add_update_footer_text' ), 999 );
186 }
187
188 /**
189 * Add Always-On HUD to WordPress Admin Bar.
190 */
191 public function add_admin_bar_hud( $wp_admin_bar ) {
192 if ( ! current_user_can( 'manage_options' ) ) {
193 return;
194 }
195
196 if ( ! self::is_admin_bar_hud_enabled() ) {
197 return;
198 }
199
200 $env = self::get_environment_type();
201 $env_color = '#d63638'; // Red for Production
202 if ( 'Local' === $env ) {
203 $env_color = '#00a32a'; // Green for Local
204 } elseif ( 'Staging' === $env ) {
205 $env_color = '#dba617'; // Orange for Staging
206 }
207
208 $php_version = substr( phpversion(), 0, 3 );
209
210 $memory_usage = memory_get_peak_usage( true );
211 $memory_limit_bytes = wp_convert_hr_to_bytes( ini_get( 'memory_limit' ) );
212
213 if ( $memory_limit_bytes > 0 ) {
214 $percentage = round( ( $memory_usage / $memory_limit_bytes ) * 100 );
215 $memory_percentage = $percentage . '%';
216 } else {
217 $memory_percentage = size_format( $memory_usage );
218 }
219
220 $badge_html = '<span style="display:inline-block; padding:0 6px; border-radius:3px; background-color:' . esc_attr( $env_color ) . '; color:#fff; font-weight:bold; font-size:11px; text-transform:uppercase; margin-right:8px; line-height:1.6;">' . esc_html( $env ) . '</span>';
221 $title_html = $badge_html . ' PHP ' . esc_html( $php_version ) . ' | RAM ' . esc_html( $memory_percentage );
222
223 $wp_admin_bar->add_node( array(
224 'id' => 'si_admin_bar_hud',
225 'title' => $title_html,
226 'href' => admin_url( 'options-general.php?page=server_info_display' ),
227 'meta' => array(
228 'title' => esc_attr__( 'Server Info', 'server-info' ),
229 ),
230 ) );
231
232 $wp_admin_bar->add_node( array(
233 'id' => 'si_hud_ip',
234 'parent' => 'si_admin_bar_hud',
235 'title' => esc_html__( 'Server IP: ', 'server-info' ) . esc_html( self::get_server_value( 'SERVER_ADDR', '127.0.0.1' ) ),
236 ) );
237
238 $web_server = self::get_server_value( 'SERVER_SOFTWARE', esc_html__( 'Unknown', 'server-info' ) );
239 if ( strlen( $web_server ) > 30 ) {
240 $web_server = substr( $web_server, 0, 27 ) . '...';
241 }
242
243 $wp_admin_bar->add_node( array(
244 'id' => 'si_hud_web_server',
245 'parent' => 'si_admin_bar_hud',
246 'title' => esc_html__( 'Web Server: ', 'server-info' ) . $web_server,
247 ) );
248
249 $wp_admin_bar->add_node( array(
250 'id' => 'si_hud_os',
251 'parent' => 'si_admin_bar_hud',
252 'title' => esc_html__( 'Operating System: ', 'server-info' ) . PHP_OS,
253 ) );
254
255 global $wp_version, $wpdb;
256 $db_version = $wpdb->db_version();
257
258 $wp_admin_bar->add_node( array(
259 'id' => 'si_hud_db',
260 'parent' => 'si_admin_bar_hud',
261 'title' => esc_html__( 'Database: ', 'server-info' ) . $db_version,
262 ) );
263
264 $wp_admin_bar->add_node( array(
265 'id' => 'si_hud_wp',
266 'parent' => 'si_admin_bar_hud',
267 'title' => esc_html__( 'WordPress: ', 'server-info' ) . $wp_version,
268 ) );
269
270 $wp_admin_bar->add_node( array(
271 'id' => 'si_hud_php_limit',
272 'parent' => 'si_admin_bar_hud',
273 'title' => esc_html__( 'PHP Memory Limit: ', 'server-info' ) . ini_get( 'memory_limit' ),
274 ) );
275
276 $wp_memory_limit = defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '40M';
277 $wp_admin_bar->add_node( array(
278 'id' => 'si_hud_wp_limit',
279 'parent' => 'si_admin_bar_hud',
280 'title' => esc_html__( 'WP Memory Limit: ', 'server-info' ) . $wp_memory_limit,
281 ) );
282 }
283
284 /**
285 * Add server info to the left side of the admin footer.
286 *
287 * @param string $text The existing footer text.
288 * @return string
289 */
290 public function add_admin_footer_text( $text ) {
291 if ( ! current_user_can( 'manage_options' ) ) {
292 return $text;
293 }
294
295 if ( ! self::is_footer_info_enabled() ) {
296 return $text;
297 }
298
299 $memory_usage = memory_get_peak_usage( true );
300 $memory_limit_bytes = wp_convert_hr_to_bytes( ini_get( 'memory_limit' ) );
301 $memory_summary = size_format( $memory_usage );
302
303 if ( $memory_limit_bytes > 0 ) {
304 $memory_summary = round( ( $memory_usage / $memory_limit_bytes ) * 100 ) . '% RAM';
305 }
306
307 $info = sprintf(
308 ' <span class="si-admin-footer-summary">| %1$s: %2$s | %3$s</span>',
309 esc_html__( 'Server Info', 'server-info' ),
310 esc_html( self::get_environment_type() ),
311 esc_html( $memory_summary )
312 );
313
314 return $text . $info;
315 }
316
317 /**
318 * Add server info to the right side of the admin footer.
319 *
320 * @param string $text The existing update footer text.
321 * @return string
322 */
323 public function add_update_footer_text( $text ) {
324 return $text;
325 }
326
327
328
329 /**
330 * Enqueues admin styles and scripts and sets dynamic theme variables.
331 *
332 * @since 0.0.1
333 * @access public
334 *
335 * @return void
336 */
337 public function admin_scripts( $hook ) {
338 if ( 'settings_page_server_info_display' !== $hook && 'index.php' !== $hook ) {
339 return;
340 }
341
342 $style_path = SERVER_INFO_PLUGIN_DIR . 'assets/css/style.css';
343 $style_version = file_exists( $style_path ) ? (string) filemtime( $style_path ) : SERVER_INFO_PLUGIN_VERSION;
344
345 wp_enqueue_style( 'server-info', SERVER_INFO_PLUGIN_URL . 'assets/css/style.css', array(), $style_version, 'all' );
346
347 wp_add_inline_style( 'server-info', self::get_appearance_css() );
348
349 if ( 'settings_page_server_info_display' === $hook ) {
350 $script_path = SERVER_INFO_PLUGIN_DIR . 'assets/js/admin.js';
351 $script_version = file_exists( $script_path ) ? (string) filemtime( $script_path ) : SERVER_INFO_PLUGIN_VERSION;
352
353 wp_enqueue_script( 'server-info-admin', SERVER_INFO_PLUGIN_URL . 'assets/js/admin.js', array(), $script_version, true );
354 wp_localize_script(
355 'server-info-admin',
356 'serverInfoAdmin',
357 array(
358 'checkoutUrl' => 'https://checkout.freemius.com/js/v1/',
359 'productId' => SERVER_INFO_FREEMIUS_PRODUCT_ID,
360 'publicKey' => SERVER_INFO_FREEMIUS_PUBLIC_KEY,
361 'productName' => 'Server Info',
362 'loadingLabel' => __( 'Opening secure checkout...', 'server-info' ),
363 'unavailableLabel' => __( 'Support checkout is temporarily unavailable. Please try again later.', 'server-info' ),
364 'thankYouLabel' => __( 'Thank you for supporting Server Info.', 'server-info' ),
365 )
366 );
367 }
368 }
369
370 /**
371 * Gets the available voluntary supporter plans.
372 *
373 * @return array
374 */
375 public static function get_support_plans() {
376 return array(
377 array(
378 'id' => SERVER_INFO_FREEMIUS_SUPPORTER_PLAN_ID,
379 'title' => esc_html__( 'Supporter', 'server-info' ),
380 'amount' => '$5',
381 'description' => esc_html__( 'A small thank-you that helps keep Server Info maintained.', 'server-info' ),
382 'featured' => true,
383 ),
384 array(
385 'id' => SERVER_INFO_FREEMIUS_BACKER_PLAN_ID,
386 'title' => esc_html__( 'Backer', 'server-info' ),
387 'amount' => '$15',
388 'description' => esc_html__( 'Fuel ongoing updates, testing, and thoughtful maintenance.', 'server-info' ),
389 ),
390 array(
391 'id' => SERVER_INFO_FREEMIUS_SPONSOR_PLAN_ID,
392 'title' => esc_html__( 'Sponsor', 'server-info' ),
393 'amount' => '$49',
394 'description' => esc_html__( 'Help shape new diagnostics and stronger, more reliable releases.', 'server-info' ),
395 ),
396 array(
397 'id' => SERVER_INFO_FREEMIUS_AGENCY_SPONSOR_PLAN_ID,
398 'title' => esc_html__( 'Agency Sponsor', 'server-info' ),
399 'amount' => '$99',
400 'description' => esc_html__( 'Sustain Server Info for agencies managing client websites.', 'server-info' ),
401 ),
402 );
403 }
404
405 /**
406 * Registers the plugin options page under Settings.
407 *
408 * @since 0.0.1
409 * @access public
410 *
411 * @return void
412 */
413 public function add_plugin_menu() {
414 add_options_page(
415 esc_html__( 'Server Information', 'server-info' ),
416 esc_html__( 'Server Info', 'server-info' ),
417 'manage_options',
418 'server_info_display',
419 array( 'Server_Info', 'display_server_info' )
420 );
421 }
422
423 /**
424 * Registers plugin settings.
425 *
426 * @return void
427 */
428 public function register_settings() {
429 register_setting(
430 'server_info_settings',
431 SERVER_INFO_OPTION_NAME,
432 array(
433 'type' => 'array',
434 'sanitize_callback' => array( 'Server_Info', 'sanitize_settings' ),
435 'default' => self::get_default_options(),
436 )
437 );
438 }
439
440 /**
441 * Gets the default plugin options.
442 *
443 * @return array
444 */
445 public static function get_default_options() {
446 return array(
447 'admin_bar_hud' => 1,
448 'footer_info' => 1,
449 'domain_expiry_lookup' => 0,
450 'appearance_scheme' => 'default',
451 'custom_bg_color' => '#f8fafc',
452 'custom_text_color' => '#0f172a',
453 );
454 }
455
456 /**
457 * Gets merged plugin options.
458 *
459 * @return array
460 */
461 public static function get_options() {
462 $options = get_option( SERVER_INFO_OPTION_NAME, array() );
463
464 if ( ! is_array( $options ) ) {
465 $options = array();
466 }
467
468 return wp_parse_args( $options, self::get_default_options() );
469 }
470
471 /**
472 * Sanitizes plugin settings.
473 *
474 * @param array $input Raw settings.
475 * @return array
476 */
477 public static function sanitize_settings( $input ) {
478 $input = is_array( $input ) ? $input : array();
479 $scheme = isset( $input['appearance_scheme'] ) ? sanitize_key( $input['appearance_scheme'] ) : 'default';
480
481 if ( ! array_key_exists( $scheme, self::get_appearance_schemes() ) ) {
482 $scheme = 'default';
483 }
484
485 $custom_bg = isset( $input['custom_bg_color'] ) ? sanitize_hex_color( $input['custom_bg_color'] ) : '';
486 $custom_text = isset( $input['custom_text_color'] ) ? sanitize_hex_color( $input['custom_text_color'] ) : '';
487
488 return array(
489 'admin_bar_hud' => empty( $input['admin_bar_hud'] ) ? 0 : 1,
490 'footer_info' => empty( $input['footer_info'] ) ? 0 : 1,
491 'domain_expiry_lookup' => empty( $input['domain_expiry_lookup'] ) ? 0 : 1,
492 'appearance_scheme' => $scheme,
493 'custom_bg_color' => $custom_bg ? $custom_bg : '#f8fafc',
494 'custom_text_color' => $custom_text ? $custom_text : '#0f172a',
495 );
496 }
497
498 /**
499 * Determines if the admin bar HUD is enabled.
500 *
501 * @return bool
502 */
503 public static function is_admin_bar_hud_enabled() {
504 $options = self::get_options();
505
506 return ! empty( $options['admin_bar_hud'] );
507 }
508
509 /**
510 * Determines if footer diagnostics are enabled.
511 *
512 * @return bool
513 */
514 public static function is_footer_info_enabled() {
515 $options = self::get_options();
516
517 return ! empty( $options['footer_info'] );
518 }
519
520 /**
521 * Determines if domain expiry lookups are enabled.
522 *
523 * @return bool
524 */
525 public static function is_domain_expiry_lookup_enabled() {
526 $options = self::get_options();
527
528 return ! empty( $options['domain_expiry_lookup'] );
529 }
530
531 /**
532 * Gets supported appearance schemes.
533 *
534 * @return array
535 */
536 public static function get_appearance_schemes() {
537 return array(
538 'default' => array(
539 'label' => esc_html__( 'Default', 'server-info' ),
540 'vars' => array(),
541 ),
542 'calm_blue' => array(
543 'label' => esc_html__( 'Calm Blue', 'server-info' ),
544 'vars' => array(
545 '--si-bg' => '#eef6ff',
546 '--si-surface' => '#ffffff',
547 '--si-border' => '#bfdbfe',
548 '--si-text-main' => '#102033',
549 '--si-text-muted' => '#4f6b87',
550 '--si-primary' => '#2563eb',
551 '--si-primary-light' => '#dbeafe',
552 ),
553 ),
554 'fresh_green' => array(
555 'label' => esc_html__( 'Fresh Green', 'server-info' ),
556 'vars' => array(
557 '--si-bg' => '#f0fdf4',
558 '--si-surface' => '#ffffff',
559 '--si-border' => '#bbf7d0',
560 '--si-text-main' => '#13241a',
561 '--si-text-muted' => '#526b5a',
562 '--si-primary' => '#15803d',
563 '--si-primary-light' => '#dcfce7',
564 ),
565 ),
566 'high_contrast' => array(
567 'label' => esc_html__( 'High Contrast', 'server-info' ),
568 'vars' => array(
569 '--si-bg' => '#111827',
570 '--si-surface' => '#ffffff',
571 '--si-border' => '#d1d5db',
572 '--si-text-main' => '#030712',
573 '--si-text-muted' => '#374151',
574 '--si-primary' => '#1d4ed8',
575 '--si-primary-light' => '#dbeafe',
576 ),
577 ),
578 'custom' => array(
579 'label' => esc_html__( 'Custom Colors', 'server-info' ),
580 'vars' => array(),
581 ),
582 );
583 }
584
585 /**
586 * Builds CSS variables for the selected appearance.
587 *
588 * @return string
589 */
590 public static function get_appearance_css() {
591 $options = self::get_options();
592 $schemes = self::get_appearance_schemes();
593 $scheme = isset( $options['appearance_scheme'] ) ? $options['appearance_scheme'] : 'default';
594 $vars = isset( $schemes[ $scheme ] ) ? $schemes[ $scheme ]['vars'] : array();
595
596 if ( 'custom' === $scheme ) {
597 $vars = array(
598 '--si-bg' => $options['custom_bg_color'],
599 '--si-text-main' => $options['custom_text_color'],
600 '--si-text-muted' => $options['custom_text_color'],
601 );
602 }
603
604 if ( empty( $vars ) ) {
605 return '';
606 }
607
608 $css = '.server-info-wrapper {';
609 foreach ( $vars as $name => $value ) {
610 $css .= sprintf( '%s:%s;', $name, $value );
611 }
612 $css .= '}';
613
614 return $css;
615 }
616
617 /**
618 * Gets the normalized public-facing site domain.
619 *
620 * @return string
621 */
622 public static function get_site_domain() {
623 $host = wp_parse_url( home_url(), PHP_URL_HOST );
624
625 if ( empty( $host ) && ! empty( $_SERVER['HTTP_HOST'] ) ) {
626 $host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) );
627 }
628
629 $host = strtolower( trim( (string) $host ) );
630 $host = preg_replace( '/:\d+$/', '', $host );
631
632 if ( function_exists( 'idn_to_ascii' ) ) {
633 $idn_host = idn_to_ascii( $host, 0, defined( 'INTL_IDNA_VARIANT_UTS46' ) ? INTL_IDNA_VARIANT_UTS46 : 0 );
634 if ( ! empty( $idn_host ) ) {
635 $host = strtolower( $idn_host );
636 }
637 }
638
639 return $host;
640 }
641
642 /**
643 * Gets the likely registered/root domain for expiry lookups.
644 *
645 * @param string $domain Site host or domain.
646 * @return string
647 */
648 public static function get_registered_domain( $domain ) {
649 $domain = strtolower( trim( (string) $domain, ". \t\n\r\0\x0B" ) );
650
651 if ( ! self::is_public_domain( $domain ) ) {
652 return $domain;
653 }
654
655 $parts = explode( '.', $domain );
656 if ( count( $parts ) <= 2 ) {
657 return $domain;
658 }
659
660 $known_second_level_suffixes = array(
661 'ac.uk',
662 'co.uk',
663 'gov.uk',
664 'ltd.uk',
665 'me.uk',
666 'net.uk',
667 'org.uk',
668 'plc.uk',
669 'com.au',
670 'net.au',
671 'org.au',
672 'com.br',
673 'net.br',
674 'org.br',
675 'com.cn',
676 'net.cn',
677 'org.cn',
678 'com.mx',
679 'co.nz',
680 'net.nz',
681 'org.nz',
682 'ac.nz',
683 'co.jp',
684 'ne.jp',
685 'or.jp',
686 'ac.jp',
687 'com.pk',
688 'net.pk',
689 'org.pk',
690 'edu.pk',
691 'gov.pk',
692 'com.tr',
693 'net.tr',
694 'org.tr',
695 );
696
697 $suffix = implode( '.', array_slice( $parts, -2 ) );
698 if ( in_array( $suffix, $known_second_level_suffixes, true ) && count( $parts ) >= 3 ) {
699 return implode( '.', array_slice( $parts, -3 ) );
700 }
701
702 return implode( '.', array_slice( $parts, -2 ) );
703 }
704
705 /**
706 * Determines whether the domain is worth probing externally.
707 *
708 * @param string $domain Domain name.
709 * @return bool
710 */
711 public static function is_public_domain( $domain ) {
712 if ( empty( $domain ) ) {
713 return false;
714 }
715
716 if ( false === strpos( $domain, '.' ) ) {
717 return false;
718 }
719
720 if ( preg_match( '/(\.local|\.test|\.localhost|\.invalid|\.example)$/', $domain ) ) {
721 return false;
722 }
723
724 if ( 'localhost' === $domain || filter_var( $domain, FILTER_VALIDATE_IP ) ) {
725 return false;
726 }
727
728 return true;
729 }
730
731 /**
732 * Gets SSL/TLS certificate metadata for the site's host.
733 *
734 * @param string $domain Domain name.
735 * @return array
736 */
737 public static function get_ssl_certificate_info( $domain ) {
738 $default = array(
739 'available' => false,
740 'status' => esc_html__( 'Unavailable', 'server-info' ),
741 'message' => esc_html__( 'SSL certificate details could not be read for this site.', 'server-info' ),
742 'issuer' => esc_html__( 'Unavailable', 'server-info' ),
743 'domain' => $domain,
744 'expiry' => esc_html__( 'Unavailable', 'server-info' ),
745 'days_left' => null,
746 'status_key' => 'neutral',
747 );
748
749 if ( ! self::is_public_domain( $domain ) ) {
750 $default['message'] = esc_html__( 'SSL lookup is skipped for local, test, or IP-based domains.', 'server-info' );
751 return $default;
752 }
753
754 $cache_key = 'si_ssl_cert_' . md5( $domain );
755 $cached = get_transient( $cache_key );
756 if ( is_array( $cached ) ) {
757 return $cached;
758 }
759
760 if ( ! function_exists( 'stream_socket_client' ) || ! function_exists( 'openssl_x509_parse' ) ) {
761 $default['message'] = esc_html__( 'The required PHP stream or OpenSSL functions are not available.', 'server-info' );
762 set_transient( $cache_key, $default, 6 * HOUR_IN_SECONDS );
763 return $default;
764 }
765
766 $context = stream_context_create(
767 array(
768 'ssl' => array(
769 'capture_peer_cert' => true,
770 'peer_name' => $domain,
771 'verify_peer' => false,
772 'verify_peer_name' => false,
773 'SNI_enabled' => true,
774 ),
775 )
776 );
777
778 $client = @stream_socket_client( 'ssl://' . $domain . ':443', $error_code, $error_message, 4, STREAM_CLIENT_CONNECT, $context );
779 if ( ! $client ) {
780 $default['message'] = $error_message ? $error_message : esc_html__( 'Connection to port 443 failed.', 'server-info' );
781 set_transient( $cache_key, $default, 2 * HOUR_IN_SECONDS );
782 return $default;
783 }
784
785 $params = stream_context_get_params( $client );
786 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- This closes a TLS network socket, not a filesystem handle.
787 fclose( $client );
788
789 if ( empty( $params['options']['ssl']['peer_certificate'] ) ) {
790 set_transient( $cache_key, $default, 2 * HOUR_IN_SECONDS );
791 return $default;
792 }
793
794 $cert = openssl_x509_parse( $params['options']['ssl']['peer_certificate'] );
795 if ( ! is_array( $cert ) ) {
796 set_transient( $cache_key, $default, 2 * HOUR_IN_SECONDS );
797 return $default;
798 }
799
800 $issuer = self::format_certificate_name( isset( $cert['issuer'] ) ? $cert['issuer'] : array() );
801 $expires = isset( $cert['validTo_time_t'] ) ? (int) $cert['validTo_time_t'] : 0;
802 $days_left = $expires > 0 ? (int) floor( ( $expires - time() ) / DAY_IN_SECONDS ) : null;
803 $matches = self::certificate_matches_domain( $cert, $domain );
804
805 $status_key = 'success';
806 $status = esc_html__( 'Valid', 'server-info' );
807 $message = esc_html__( 'Certificate is active for this domain.', 'server-info' );
808
809 if ( null === $days_left ) {
810 $status_key = 'neutral';
811 $status = esc_html__( 'Unknown', 'server-info' );
812 $message = esc_html__( 'Certificate expiry date is not available.', 'server-info' );
813 } elseif ( $days_left < 0 ) {
814 $status_key = 'danger';
815 $status = esc_html__( 'Expired', 'server-info' );
816 $message = esc_html__( 'Certificate has expired.', 'server-info' );
817 } elseif ( $days_left <= 30 ) {
818 $status_key = 'warning';
819 $status = esc_html__( 'Expiring Soon', 'server-info' );
820 $message = esc_html__( 'Certificate expires within 30 days.', 'server-info' );
821 }
822
823 if ( ! $matches ) {
824 $status_key = 'danger';
825 $status = esc_html__( 'Domain Mismatch', 'server-info' );
826 $message = esc_html__( 'Certificate does not appear to match the site domain.', 'server-info' );
827 }
828
829 $result = array(
830 'available' => true,
831 'status' => $status,
832 'message' => $message,
833 'issuer' => $issuer ? $issuer : esc_html__( 'Unknown issuer', 'server-info' ),
834 'domain' => $domain,
835 'expiry' => $expires > 0 ? date_i18n( get_option( 'date_format' ), $expires ) : esc_html__( 'Unavailable', 'server-info' ),
836 'days_left' => $days_left,
837 'status_key' => $status_key,
838 );
839
840 set_transient( $cache_key, $result, 12 * HOUR_IN_SECONDS );
841
842 return $result;
843 }
844
845 /**
846 * Formats certificate subject/issuer arrays.
847 *
848 * @param array $parts Certificate name parts.
849 * @return string
850 */
851 public static function format_certificate_name( $parts ) {
852 if ( ! is_array( $parts ) || empty( $parts ) ) {
853 return '';
854 }
855
856 foreach ( array( 'CN', 'O', 'OU' ) as $key ) {
857 if ( ! empty( $parts[ $key ] ) ) {
858 return is_array( $parts[ $key ] ) ? implode( ', ', array_map( 'sanitize_text_field', $parts[ $key ] ) ) : sanitize_text_field( $parts[ $key ] );
859 }
860 }
861
862 return '';
863 }
864
865 /**
866 * Checks certificate common name/SAN coverage.
867 *
868 * @param array $cert Parsed certificate.
869 * @param string $domain Domain name.
870 * @return bool
871 */
872 public static function certificate_matches_domain( $cert, $domain ) {
873 $names = array();
874
875 if ( ! empty( $cert['subject']['CN'] ) ) {
876 $names[] = $cert['subject']['CN'];
877 }
878
879 if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
880 $alt_names = explode( ',', $cert['extensions']['subjectAltName'] );
881 foreach ( $alt_names as $alt_name ) {
882 $alt_name = trim( $alt_name );
883 if ( 0 === stripos( $alt_name, 'DNS:' ) ) {
884 $names[] = substr( $alt_name, 4 );
885 }
886 }
887 }
888
889 foreach ( $names as $name ) {
890 $name = strtolower( trim( $name ) );
891 if ( $name === $domain ) {
892 return true;
893 }
894
895 if ( 0 === strpos( $name, '*.' ) ) {
896 $suffix = substr( $name, 1 );
897 if ( substr( $domain, -strlen( $suffix ) ) === $suffix && substr_count( $domain, '.' ) === substr_count( $suffix, '.' ) ) {
898 return true;
899 }
900 }
901 }
902
903 return false;
904 }
905
906 /**
907 * Gets best-effort WHOIS domain expiry details.
908 *
909 * @param string $domain Domain name.
910 * @return array
911 */
912 public static function get_domain_expiry_info( $domain ) {
913 $default = array(
914 'available' => false,
915 'status' => esc_html__( 'Unavailable', 'server-info' ),
916 'message' => esc_html__( 'Domain expiry could not be determined.', 'server-info' ),
917 'registrar' => esc_html__( 'Unavailable', 'server-info' ),
918 'domain' => $domain,
919 'expiry' => esc_html__( 'Unavailable', 'server-info' ),
920 'days_left' => null,
921 'status_key' => 'neutral',
922 );
923
924 if ( ! self::is_domain_expiry_lookup_enabled() ) {
925 $default['message'] = esc_html__( 'Domain expiry lookup is disabled in Server Info settings.', 'server-info' );
926 return $default;
927 }
928
929 if ( ! self::is_public_domain( $domain ) ) {
930 $default['message'] = esc_html__( 'Domain expiry lookup is skipped for local, test, or IP-based domains.', 'server-info' );
931 return $default;
932 }
933
934 $cache_key = 'si_domain_expiry_' . md5( $domain );
935 $cached = get_transient( $cache_key );
936 if ( is_array( $cached ) ) {
937 return $cached;
938 }
939
940 $parsed = self::query_rdap_domain( $domain );
941
942 if ( empty( $parsed['expiry_timestamp'] ) ) {
943 $whois_server = self::get_whois_server_for_domain( $domain );
944 if ( empty( $whois_server ) ) {
945 $default['message'] = esc_html__( 'No RDAP or WHOIS expiry data is available for this domain extension.', 'server-info' );
946 set_transient( $cache_key, $default, 12 * HOUR_IN_SECONDS );
947 return $default;
948 }
949
950 $raw_whois = self::query_whois( $whois_server, $domain );
951 if ( empty( $raw_whois ) ) {
952 $default['message'] = esc_html__( 'Domain lookup timed out or returned no data.', 'server-info' );
953 set_transient( $cache_key, $default, 6 * HOUR_IN_SECONDS );
954 return $default;
955 }
956
957 $parsed = self::parse_whois_expiry( $raw_whois );
958 }
959
960 if ( empty( $parsed['expiry_timestamp'] ) ) {
961 $default['message'] = esc_html__( 'Domain lookup did not include a recognizable expiry date.', 'server-info' );
962 set_transient( $cache_key, $default, 12 * HOUR_IN_SECONDS );
963 return $default;
964 }
965
966 $days_left = (int) floor( ( $parsed['expiry_timestamp'] - time() ) / DAY_IN_SECONDS );
967 $status_key = 'success';
968 $status = esc_html__( 'Active', 'server-info' );
969 $message = esc_html__( 'Domain registration appears active.', 'server-info' );
970
971 if ( $days_left < 0 ) {
972 $status_key = 'danger';
973 $status = esc_html__( 'Expired', 'server-info' );
974 $message = esc_html__( 'Domain registration appears expired.', 'server-info' );
975 } elseif ( $days_left <= 30 ) {
976 $status_key = 'warning';
977 $status = esc_html__( 'Expiring Soon', 'server-info' );
978 $message = esc_html__( 'Domain expires within 30 days.', 'server-info' );
979 }
980
981 $result = array(
982 'available' => true,
983 'status' => $status,
984 'message' => $message,
985 'registrar' => ! empty( $parsed['registrar'] ) ? $parsed['registrar'] : esc_html__( 'Unknown registrar', 'server-info' ),
986 'domain' => $domain,
987 'expiry' => date_i18n( get_option( 'date_format' ), $parsed['expiry_timestamp'] ),
988 'days_left' => $days_left,
989 'status_key' => $status_key,
990 );
991
992 set_transient( $cache_key, $result, DAY_IN_SECONDS );
993
994 return $result;
995 }
996
997 /**
998 * Queries RDAP for domain registration metadata.
999 *
1000 * @param string $domain Domain name.
1001 * @return array
1002 */
1003 public static function query_rdap_domain( $domain ) {
1004 $urls = self::get_rdap_urls_for_domain( $domain );
1005
1006 foreach ( $urls as $url ) {
1007 $response = wp_remote_get(
1008 $url,
1009 array(
1010 'timeout' => 5,
1011 'redirection' => 3,
1012 'user-agent' => 'Server Info/' . SERVER_INFO_PLUGIN_VERSION . '; ' . home_url( '/' ),
1013 )
1014 );
1015
1016 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
1017 continue;
1018 }
1019
1020 $body = json_decode( wp_remote_retrieve_body( $response ), true );
1021 if ( ! is_array( $body ) ) {
1022 continue;
1023 }
1024
1025 $parsed = self::parse_rdap_expiry( $body );
1026 if ( ! empty( $parsed['expiry_timestamp'] ) ) {
1027 return $parsed;
1028 }
1029 }
1030
1031 return array();
1032 }
1033
1034 /**
1035 * Gets RDAP endpoint candidates for a domain.
1036 *
1037 * @param string $domain Domain name.
1038 * @return array
1039 */
1040 public static function get_rdap_urls_for_domain( $domain ) {
1041 $parts = explode( '.', $domain );
1042 $tld = end( $parts );
1043
1044 $direct = array(
1045 'com' => 'https://rdap.verisign.com/com/v1/domain/',
1046 'net' => 'https://rdap.verisign.com/net/v1/domain/',
1047 'org' => 'https://rdap.publicinterestregistry.org/rdap/domain/',
1048 );
1049
1050 $urls = array();
1051 if ( isset( $direct[ $tld ] ) ) {
1052 $urls[] = $direct[ $tld ] . rawurlencode( $domain );
1053 }
1054
1055 $urls[] = 'https://rdap.org/domain/' . rawurlencode( $domain );
1056
1057 return $urls;
1058 }
1059
1060 /**
1061 * Parses registrar and expiry values from RDAP data.
1062 *
1063 * @param array $data RDAP response.
1064 * @return array
1065 */
1066 public static function parse_rdap_expiry( $data ) {
1067 $expiry_timestamp = 0;
1068 if ( ! empty( $data['events'] ) && is_array( $data['events'] ) ) {
1069 foreach ( $data['events'] as $event ) {
1070 if ( empty( $event['eventAction'] ) || empty( $event['eventDate'] ) ) {
1071 continue;
1072 }
1073
1074 if ( in_array( strtolower( $event['eventAction'] ), array( 'expiration', 'expiry' ), true ) ) {
1075 $timestamp = strtotime( $event['eventDate'] );
1076 if ( false !== $timestamp ) {
1077 $expiry_timestamp = $timestamp;
1078 break;
1079 }
1080 }
1081 }
1082 }
1083
1084 $registrar = '';
1085 if ( ! empty( $data['entities'] ) && is_array( $data['entities'] ) ) {
1086 foreach ( $data['entities'] as $entity ) {
1087 $roles = ! empty( $entity['roles'] ) && is_array( $entity['roles'] ) ? array_map( 'strtolower', $entity['roles'] ) : array();
1088 if ( ! in_array( 'registrar', $roles, true ) ) {
1089 continue;
1090 }
1091
1092 $registrar = self::extract_rdap_vcard_name( $entity );
1093 if ( $registrar ) {
1094 break;
1095 }
1096 }
1097 }
1098
1099 return array(
1100 'expiry_timestamp' => $expiry_timestamp,
1101 'registrar' => $registrar,
1102 );
1103 }
1104
1105 /**
1106 * Extracts a display name from an RDAP vCard entity.
1107 *
1108 * @param array $entity RDAP entity.
1109 * @return string
1110 */
1111 public static function extract_rdap_vcard_name( $entity ) {
1112 if ( empty( $entity['vcardArray'][1] ) || ! is_array( $entity['vcardArray'][1] ) ) {
1113 return '';
1114 }
1115
1116 foreach ( $entity['vcardArray'][1] as $item ) {
1117 if ( is_array( $item ) && isset( $item[0], $item[3] ) && 'fn' === strtolower( $item[0] ) ) {
1118 return sanitize_text_field( $item[3] );
1119 }
1120 }
1121
1122 return '';
1123 }
1124
1125 /**
1126 * Maps common TLDs to WHOIS servers.
1127 *
1128 * @param string $domain Domain name.
1129 * @return string
1130 */
1131 public static function get_whois_server_for_domain( $domain ) {
1132 $parts = explode( '.', $domain );
1133 $tld = end( $parts );
1134
1135 $servers = array(
1136 'com' => 'whois.verisign-grs.com',
1137 'net' => 'whois.verisign-grs.com',
1138 'org' => 'whois.pir.org',
1139 'info' => 'whois.afilias.net',
1140 'biz' => 'whois.biz',
1141 'us' => 'whois.nic.us',
1142 'co' => 'whois.nic.co',
1143 'io' => 'whois.nic.io',
1144 'me' => 'whois.nic.me',
1145 'tv' => 'whois.nic.tv',
1146 'dev' => 'whois.nic.google',
1147 'app' => 'whois.nic.google',
1148 'uk' => 'whois.nic.uk',
1149 'ca' => 'whois.cira.ca',
1150 'au' => 'whois.auda.org.au',
1151 'pk' => 'whois.pknic.net.pk',
1152 );
1153
1154 return isset( $servers[ $tld ] ) ? $servers[ $tld ] : '';
1155 }
1156
1157 /**
1158 * Queries a WHOIS server with a short timeout.
1159 *
1160 * @param string $server WHOIS server.
1161 * @param string $domain Domain name.
1162 * @return string
1163 */
1164 public static function query_whois( $server, $domain ) {
1165 if ( ! function_exists( 'fsockopen' ) ) {
1166 return '';
1167 }
1168
1169 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fsockopen -- WHOIS is a TCP protocol and cannot use WP_Filesystem.
1170 $connection = @fsockopen( $server, 43, $error_code, $error_message, 4 );
1171 if ( ! $connection ) {
1172 return '';
1173 }
1174
1175 stream_set_timeout( $connection, 4 );
1176 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Writing a WHOIS query to a network socket.
1177 fwrite( $connection, $domain . "\r\n" );
1178
1179 $response = '';
1180 while ( ! feof( $connection ) ) {
1181 $response .= fgets( $connection, 1024 );
1182 if ( strlen( $response ) > 20000 ) {
1183 break;
1184 }
1185 }
1186 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- This closes a WHOIS network socket.
1187 fclose( $connection );
1188
1189 return $response;
1190 }
1191
1192 /**
1193 * Parses registrar and expiry values from a WHOIS response.
1194 *
1195 * @param string $response WHOIS response.
1196 * @return array
1197 */
1198 public static function parse_whois_expiry( $response ) {
1199 $expiry_patterns = array(
1200 '/Registry Expiry Date:\s*(.+)/i',
1201 '/Registrar Registration Expiration Date:\s*(.+)/i',
1202 '/Expiration Date:\s*(.+)/i',
1203 '/Expiry Date:\s*(.+)/i',
1204 '/Renewal Date:\s*(.+)/i',
1205 '/paid-till:\s*(.+)/i',
1206 '/expires:\s*(.+)/i',
1207 );
1208
1209 $registrar_patterns = array(
1210 '/Registrar:\s*(.+)/i',
1211 '/Sponsoring Registrar:\s*(.+)/i',
1212 '/registrar:\s*(.+)/i',
1213 );
1214
1215 $expiry_timestamp = 0;
1216 foreach ( $expiry_patterns as $pattern ) {
1217 if ( preg_match( $pattern, $response, $matches ) ) {
1218 $date = trim( $matches[1] );
1219 $date = preg_replace( '/\s+\(.+\)$/', '', $date );
1220 $timestamp = strtotime( $date );
1221 if ( false !== $timestamp ) {
1222 $expiry_timestamp = $timestamp;
1223 break;
1224 }
1225 }
1226 }
1227
1228 $registrar = '';
1229 foreach ( $registrar_patterns as $pattern ) {
1230 if ( preg_match( $pattern, $response, $matches ) ) {
1231 $registrar = sanitize_text_field( trim( $matches[1] ) );
1232 break;
1233 }
1234 }
1235
1236 return array(
1237 'expiry_timestamp' => $expiry_timestamp,
1238 'registrar' => $registrar,
1239 );
1240 }
1241
1242 /**
1243 * Registers the dashboard widget.
1244 *
1245 * @since 0.0.1
1246 * @access public
1247 *
1248 * @return void
1249 */
1250 public function add_dashboard_widgets() {
1251 wp_add_dashboard_widget(
1252 'serverinfo_dashboard_widget',
1253 esc_html__( 'Server Info', 'server-info' ),
1254 array( 'Server_Info', 'display_dashboard_widget' )
1255 );
1256 }
1257
1258 /**
1259 * Renders the dashboard widget content.
1260 *
1261 * @since 0.0.1
1262 * @access public
1263 *
1264 * @return void
1265 */
1266 public static function display_dashboard_widget() {
1267 $info = self::site_info();
1268 ?>
1269 <div class="si-card" style="box-shadow: none; padding: 0; background: transparent;">
1270 <ul class="si-list">
1271 <?php
1272 $fields_to_show = array( 'operating_system', 'server_ip', 'server_hostname', 'php_version' );
1273 foreach ( $fields_to_show as $key ) {
1274 if ( isset( $info['wp-server']['fields'][ $key ] ) ) {
1275 $field = $info['wp-server']['fields'][ $key ];
1276 ?>
1277 <li class="si-list-item">
1278 <span class="si-item-label"><?php echo esc_html( $field['label'] ); ?></span>
1279 <span class="si-item-value"><?php echo esc_html( $field['value'] ); ?></span>
1280 </li>
1281 <?php
1282 }
1283 }
1284 ?>
1285 </ul>
1286 <div style="margin-top: 15px;">
1287 <a class="si-btn" style="width: 100%; text-align: center; display: block;" href="<?php echo esc_url( admin_url( 'options-general.php?page=server_info_display' ) ); ?>"><?php esc_html_e( 'View More Information', 'server-info' ); ?></a>
1288 </div>
1289 </div>
1290 <?php
1291 }
1292
1293 /**
1294 * Gathers and structures all server and WordPress information.
1295 *
1296 * @since 0.0.1
1297 * @access public
1298 *
1299 * @return array Multi-dimensional array containing server data.
1300 */
1301 public static function site_info() {
1302 global $wpdb;
1303
1304 // Set up the array that holds all information.
1305 $info = array();
1306
1307 $info['wp-server'] = array(
1308 'label' => esc_html__( 'Hosting Server Information', 'server-info' ),
1309 'fields' => array(),
1310 );
1311
1312 if ( function_exists( 'phpversion' ) ) {
1313 $php_version_debug = phpversion();
1314 // Whether PHP supports 64-bit.
1315 $php64bit = ( 64 === PHP_INT_SIZE * 8 );
1316
1317 $php_version = $php_version_debug;
1318 } else {
1319 $php_version = esc_html__( 'Unable to determine PHP version', 'server-info' );
1320 }
1321
1322 if ( function_exists( 'php_uname' ) ) {
1323 $server_architecture = sprintf( '%s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'm' ) );
1324 $os_family = php_uname( 's' );
1325
1326 // Extract specific Linux distribution based on community feedback
1327 if ( 'Linux' === $os_family ) {
1328 if ( @is_readable( '/etc/os-release' ) ) {
1329 $os_release = @parse_ini_file( '/etc/os-release' );
1330 if ( ! empty( $os_release['PRETTY_NAME'] ) ) {
1331 $server_architecture .= ' (' . trim( $os_release['PRETTY_NAME'], '"\'' ) . ')';
1332 }
1333 } elseif ( @is_readable( '/etc/issue' ) ) {
1334 $issue = @file_get_contents( '/etc/issue' );
1335 if ( $issue ) {
1336 $parts = explode( '\\', $issue );
1337 if ( ! empty( $parts[0] ) ) {
1338 $server_architecture .= ' (' . trim( $parts[0] ) . ')';
1339 }
1340 }
1341 }
1342 }
1343 } else {
1344 $server_architecture = 'unknown';
1345 }
1346 $info['wp-server']['fields']['operating_system'] = array(
1347 'label' => esc_html__( 'Operating System', 'server-info' ),
1348 'value' => ( 'unknown' !== $server_architecture ? $server_architecture : esc_html__( 'Unable to determine server architecture', 'server-info' ) )
1349 );
1350
1351 if ( function_exists( 'php_uname' ) ) {
1352 $info['wp-server']['fields']['server_hostname'] = array(
1353 'label' => esc_html__( 'Server Hostname', 'server-info' ),
1354 'value' => php_uname( 'n' )
1355 );
1356 }
1357
1358 $server_ip = self::get_server_value( 'SERVER_ADDR' );
1359 if ( '' !== $server_ip ) {
1360 $info['wp-server']['fields']['server_ip'] = array(
1361 'label' => esc_html__( 'Server IP', 'server-info' ),
1362 'value' => $server_ip,
1363 );
1364 }
1365
1366 $server_protocol = self::get_server_value( 'SERVER_PROTOCOL' );
1367 if ( '' !== $server_protocol ) {
1368 $info['wp-server']['fields']['server_protocol'] = array(
1369 'label' => esc_html__( 'Server Protocol', 'server-info' ),
1370 'value' => $server_protocol,
1371 );
1372 }
1373
1374 $server_admin = self::get_server_value( 'SERVER_ADMIN' );
1375 if ( '' !== $server_admin ) {
1376 $info['wp-server']['fields']['server_administrator'] = array(
1377 'label' => esc_html__( 'Server Administrator', 'server-info' ),
1378 'value' => $server_admin,
1379 );
1380 }
1381
1382 $server_port = self::get_server_value( 'SERVER_PORT' );
1383 if ( '' !== $server_port ) {
1384 $info['wp-server']['fields']['server_web_port'] = array(
1385 'label' => esc_html__( 'Server Web Port', 'server-info' ),
1386 'value' => $server_port,
1387 );
1388 }
1389
1390 $uptime = '';
1391 $disable_functions = ini_get( 'disable_functions' );
1392 if ( function_exists( 'exec' ) && is_callable( 'exec' ) && ( ! is_string( $disable_functions ) || false === stripos( $disable_functions, 'exec' ) ) ) {
1393 $uptime = @exec( "uptime" );
1394 }
1395 if ( ! empty( $uptime ) ) {
1396 $info['wp-server']['fields']['system_uptime'] = array(
1397 'label' => esc_html__( 'System Uptime', 'server-info' ),
1398 'value' => esc_html( $uptime )
1399 );
1400 }
1401
1402 if ( function_exists( 'sys_getloadavg' ) ) {
1403 $load = sys_getloadavg();
1404 if ( ! empty( $load ) ) {
1405 $info['wp-server']['fields']['load_average'] = array(
1406 'label' => esc_html__( 'Load Average', 'server-info' ),
1407 'value' => implode( ', ', $load )
1408 );
1409 }
1410 }
1411
1412 if ( function_exists( 'memory_get_usage' ) ) {
1413 $info['wp-server']['fields']['memory_usage'] = array(
1414 'label' => esc_html__( 'PHP Memory Usage', 'server-info' ),
1415 'value' => number_format( memory_get_usage( true ) / 1048576, 2 ) . ' MB'
1416 );
1417 }
1418
1419 $extensions = array( 'curl', 'mbstring', 'gd', 'imagick', 'zip', 'redis', 'memcached', 'opcache' );
1420 $active_exts = array();
1421 foreach ( $extensions as $ext ) {
1422 if ( extension_loaded( $ext ) ) {
1423 $active_exts[] = $ext;
1424 }
1425 }
1426 if ( ! empty( $active_exts ) ) {
1427 $info['wp-server']['fields']['active_extensions'] = array(
1428 'label' => esc_html__( 'Active PHP Extensions', 'server-info' ),
1429 'value' => implode( ', ', $active_exts )
1430 );
1431 }
1432
1433 $wp_config_path = ABSPATH . 'wp-config.php';
1434 if ( file_exists( $wp_config_path ) ) {
1435 $info['wp-server']['fields']['wp_config_perms'] = array(
1436 'label' => esc_html__( 'wp-config.php Permissions', 'server-info' ),
1437 'value' => substr( sprintf( '%o', fileperms( $wp_config_path ) ), -4 )
1438 );
1439 }
1440
1441 $upload_dir = wp_upload_dir();
1442 $uploads_path = $upload_dir['basedir'];
1443 $content_path = WP_CONTENT_DIR;
1444
1445 if ( file_exists( $content_path ) ) {
1446 $info['wp-server']['fields']['wp_content_perms'] = array(
1447 'label' => esc_html__( 'wp-content Permissions', 'server-info' ),
1448 'value' => substr( sprintf( '%o', fileperms( $content_path ) ), -4 ),
1449 );
1450 }
1451
1452 if ( file_exists( $uploads_path ) ) {
1453 $info['wp-server']['fields']['uploads_perms'] = array(
1454 'label' => esc_html__( 'Uploads Directory Permissions', 'server-info' ),
1455 'value' => substr( sprintf( '%o', fileperms( $uploads_path ) ), -4 ),
1456 );
1457 }
1458
1459 $info['wp-server']['fields']['httpd_software'] = array(
1460 'label' => esc_html__( 'Web server', 'server-info' ),
1461 'value' => self::get_server_value( 'SERVER_SOFTWARE', esc_html__( 'Unable to determine what web server software is used', 'server-info' ) ),
1462 );
1463
1464 $info['wp-server']['fields']['php_version'] = array(
1465 'label' => esc_html__( 'PHP version', 'server-info' ),
1466 'value' => $php_version,
1467 );
1468
1469 // Some servers disable `ini_set()` and `ini_get()`, we check this before trying to get configuration values.
1470 if ( function_exists( 'ini_get' ) ) {
1471 $info['wp-server']['fields']['memory_limit'] = array(
1472 'label' => esc_html__( 'PHP memory limit', 'server-info' ),
1473 'value' => ini_get( 'memory_limit' ),
1474 );
1475 }
1476
1477 $info['wp-server']['fields']['server_timezone'] = array(
1478 'label' => esc_html__( 'Server Timezone', 'server-info' ),
1479 'value' => date_default_timezone_get(),
1480 );
1481
1482 $gateway_interface = self::get_server_value( 'GATEWAY_INTERFACE' );
1483 if ( '' !== $gateway_interface ) {
1484 $info['wp-server']['fields']['CGI_version'] = array(
1485 'label' => esc_html__( 'CGI Version', 'server-info' ),
1486 'value' => $gateway_interface,
1487 );
1488 }
1489
1490 /**
1491 * Database
1492 */
1493 $info['wp-database'] = array(
1494 'label' => esc_html__( 'Database', 'server-info' ),
1495 'fields' => array(),
1496 );
1497
1498 // Populate the database fields.
1499 if ( is_resource( $wpdb->dbh ) ) {
1500 // Old mysql extension.
1501 $extension = 'mysql';
1502 } else if ( is_object( $wpdb->dbh ) ) {
1503 // mysqli or PDO.
1504 $extension = get_class( $wpdb->dbh );
1505 } else {
1506 // Unknown sql extension.
1507 $extension = null;
1508 }
1509
1510 $server = $wpdb->db_version();
1511
1512 if ( isset( $wpdb->use_mysqli ) && $wpdb->use_mysqli && isset( $wpdb->dbh->client_info ) ) {
1513 $client_version = $wpdb->dbh->client_info;
1514 } else {
1515 $client_version = null;
1516 }
1517
1518 $info['wp-database']['fields']['extension'] = array(
1519 'label' => esc_html__( 'Extension', 'server-info' ),
1520 'value' => $extension,
1521 );
1522
1523 $info['wp-database']['fields']['server_version'] = array(
1524 'label' => esc_html__( 'Server version', 'server-info' ),
1525 'value' => $server,
1526 );
1527
1528 $info['wp-database']['fields']['client_version'] = array(
1529 'label' => esc_html__( 'Client version', 'server-info' ),
1530 'value' => $client_version,
1531 );
1532
1533 $info['wp-database']['fields']['database_user'] = array(
1534 'label' => esc_html__( 'Database username', 'server-info' ),
1535 'value' => $wpdb->dbuser,
1536 'private' => true,
1537 );
1538
1539 $info['wp-database']['fields']['database_host'] = array(
1540 'label' => esc_html__( 'Database host', 'server-info' ),
1541 'value' => $wpdb->dbhost,
1542 'private' => true,
1543 );
1544
1545 $info['wp-database']['fields']['database_name'] = array(
1546 'label' => esc_html__( 'Database name', 'server-info' ),
1547 'value' => $wpdb->dbname,
1548 'private' => true,
1549 );
1550
1551 $db_size_query = $wpdb->prepare(
1552 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = %s',
1553 $wpdb->dbname
1554 );
1555 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared immediately above; live diagnostic data should not be cached.
1556 $db_size = $wpdb->get_var( $db_size_query );
1557 if ( $db_size ) {
1558 $info['wp-database']['fields']['database_size'] = array(
1559 'label' => esc_html__( 'Total Database size', 'server-info' ),
1560 'value' => number_format( $db_size / 1048576, 2 ) . ' MB',
1561 );
1562
1563 // Top 5 largest tables
1564 $top_tables_query = $wpdb->prepare( "
1565 SELECT table_name AS name,
1566 round(((data_length + index_length) / 1024 / 1024), 2) AS size_mb
1567 FROM information_schema.TABLES
1568 WHERE table_schema = %s
1569 ORDER BY (data_length + index_length) DESC
1570 LIMIT 5
1571 ", $wpdb->dbname );
1572 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared immediately above; live diagnostic data should not be cached.
1573 $top_tables = $wpdb->get_results( $top_tables_query );
1574
1575 if ( ! empty( $top_tables ) ) {
1576 $tables_html = array();
1577 foreach ( $top_tables as $tbl ) {
1578 $tables_html[ $tbl->name ] = $tbl->size_mb . ' MB';
1579 }
1580 $info['wp-database']['fields']['top_tables'] = array(
1581 'label' => esc_html__( 'Top 5 Largest Tables', 'server-info' ),
1582 'value' => $tables_html,
1583 );
1584 }
1585 }
1586
1587 $info['wp-database']['fields']['database_prefix'] = array(
1588 'label' => esc_html__( 'Table prefix', 'server-info' ),
1589 'value' => $wpdb->prefix,
1590 'private' => true,
1591 );
1592
1593 $info['wp-database']['fields']['database_charset'] = array(
1594 'label' => esc_html__( 'Database charset', 'server-info' ),
1595 'value' => $wpdb->charset,
1596 'private' => true,
1597 );
1598
1599 $info['wp-database']['fields']['database_collate'] = array(
1600 'label' => esc_html__( 'Database collation', 'server-info' ),
1601 'value' => $wpdb->collate,
1602 'private' => true,
1603 );
1604
1605 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Live server variables should not be cached by the object cache.
1606 $max_connections = $wpdb->get_var( "SHOW VARIABLES LIKE 'max_connections'", 1 );
1607 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Live server variables should not be cached by the object cache.
1608 $max_allowed_packet = $wpdb->get_var( "SHOW VARIABLES LIKE 'max_allowed_packet'", 1 );
1609
1610 if ( $max_connections ) {
1611 $info['wp-database']['fields']['max_connections'] = array(
1612 'label' => esc_html__( 'Max Connections', 'server-info' ),
1613 'value' => $max_connections,
1614 );
1615 }
1616
1617 if ( $max_allowed_packet ) {
1618 $info['wp-database']['fields']['max_allowed_packet'] = array(
1619 'label' => esc_html__( 'Max Allowed Packet', 'server-info' ),
1620 'value' => size_format( $max_allowed_packet ),
1621 );
1622 }
1623
1624 /**
1625 * Caching & Performance
1626 */
1627 $info['wp-caching'] = array(
1628 'label' => esc_html__( 'Caching & Performance', 'server-info' ),
1629 'fields' => array(),
1630 );
1631
1632 $object_cache_file = WP_CONTENT_DIR . '/object-cache.php';
1633 $info['wp-caching']['fields']['object_cache'] = array(
1634 'label' => esc_html__( 'Object Cache Drop-in', 'server-info' ),
1635 'value' => file_exists( $object_cache_file ) ? esc_html__( 'Active', 'server-info' ) : esc_html__( 'Inactive', 'server-info' ),
1636 );
1637
1638 if ( function_exists( 'opcache_get_status' ) ) {
1639 // Suppress warnings in case OPcache is restricted by config
1640 $opcache = @opcache_get_status( false );
1641 if ( is_array( $opcache ) && ! empty( $opcache['opcache_enabled'] ) ) {
1642 $hit_rate = 0;
1643 if ( isset( $opcache['opcache_statistics']['opcache_hit_rate'] ) ) {
1644 $hit_rate = round( $opcache['opcache_statistics']['opcache_hit_rate'], 2 );
1645 }
1646 $info['wp-caching']['fields']['opcache_status'] = array(
1647 'label' => esc_html__( 'OPcache Status', 'server-info' ),
1648 /* translators: %s: OPcache hit-rate percentage. */
1649 'value' => sprintf( esc_html__( 'Enabled (Hit Rate: %s%%)', 'server-info' ), $hit_rate ),
1650 );
1651 } else {
1652 $info['wp-caching']['fields']['opcache_status'] = array(
1653 'label' => esc_html__( 'OPcache Status', 'server-info' ),
1654 'value' => esc_html__( 'Disabled or Restricted', 'server-info' ),
1655 );
1656 }
1657 }
1658
1659 /**
1660 * WordPress Information
1661 */
1662 $is_multisite = is_multisite();
1663 $info['wp-info'] = array(
1664 'label' => esc_html__( 'WordPress Information', 'server-info' ),
1665 'fields' => array(
1666 'multisite' => array(
1667 'label' => esc_html__( 'Is this a multisite?', 'server-info' ),
1668 'value' => $is_multisite ? esc_html__( 'Yes', 'server-info' ) : esc_html__( 'No', 'server-info' ),
1669 ),
1670 ),
1671 );
1672
1673 if ( is_multisite() ) {
1674 $network_query = new WP_Network_Query();
1675 $network_ids = $network_query->query(
1676 array(
1677 'fields' => 'ids',
1678 'number' => 100,
1679 'no_found_rows' => false,
1680 )
1681 );
1682
1683 $site_count = 0;
1684 foreach ( $network_ids as $network_id ) {
1685 $site_count += get_blog_count( $network_id );
1686 }
1687
1688 $info['wp-info']['fields']['user_count'] = array(
1689 'label' => esc_html__( 'User count', 'server-info' ),
1690 'value' => get_user_count(),
1691 );
1692
1693 $info['wp-info']['fields']['site_count'] = array(
1694 'label' => esc_html__( 'Site count', 'server-info' ),
1695 'value' => $site_count,
1696 );
1697
1698 $info['wp-info']['fields']['network_count'] = array(
1699 'label' => esc_html__( 'Network count', 'server-info' ),
1700 'value' => $network_query->found_networks,
1701 );
1702 } else {
1703 $user_count = count_users();
1704
1705 $info['wp-info']['fields']['user_count'] = array(
1706 'label' => esc_html__( 'User count', 'server-info' ),
1707 'value' => $user_count['total_users'],
1708 );
1709 }
1710
1711 $active_theme = wp_get_theme();
1712 $info['wp-info']['fields'] = array(
1713 'name' => array(
1714 'label' => esc_html__( 'Active Theme', 'server-info' ),
1715 'value' => sprintf(
1716 /* translators: 1: Theme name, 2: Theme stylesheet directory. */
1717 esc_html__( '%1$s (%2$s)', 'server-info' ),
1718 $active_theme->name,
1719 $active_theme->stylesheet
1720 ),
1721 ),
1722 );
1723
1724 // List all available plugins.
1725 if ( ! function_exists( 'get_plugins' ) ) {
1726 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1727 }
1728 $plugins = get_plugins();
1729 $plugins_active = array();
1730 $plugins_inactive = array();
1731
1732 foreach ( $plugins as $plugin_path => $plugin ) {
1733 $plugin_author = $plugin['Author'];
1734
1735 if ( ! empty( $plugin_author ) ) {
1736 /* translators: %s: Plugin author name. */
1737 $plugin_author = sprintf( esc_html__( 'By %s', 'server-info' ), $plugin_author );
1738 } else {
1739 $plugin_author = '';
1740 }
1741
1742 if ( is_plugin_active( $plugin_path ) ) {
1743 $plugins_active[ $plugin['Name'] ] = $plugin_author;
1744 } else {
1745 $plugins_inactive[ $plugin['Name'] ] = $plugin_author;
1746 }
1747 }
1748
1749 if ( empty( $plugins_active ) ) {
1750 $plugins_active = esc_html__( 'None', 'server-info' );
1751 }
1752 $info['wp-info']['fields']['plugins_active'] = array(
1753 'label' => esc_html__( 'Active Plugins', 'server-info' ),
1754 'value' => $plugins_active,
1755 );
1756
1757 if ( empty( $plugins_inactive ) ) {
1758 $plugins_inactive = esc_html__( 'None', 'server-info' );
1759 }
1760 $info['wp-info']['fields']['plugins_inactive'] = array(
1761 'label' => esc_html__( 'Inactive Plugins', 'server-info' ),
1762 'value' => $plugins_inactive,
1763 );
1764
1765 $info['wp-info']['fields']['WP_MEMORY_LIMIT'] = array(
1766 'label' => esc_html__( 'WordPress Memory Limit', 'server-info' ),
1767 'value' => WP_MEMORY_LIMIT,
1768 );
1769
1770 $info['wp-info']['fields']['WP_MAX_MEMORY_LIMIT'] = array(
1771 'label' => esc_html__( 'WordPress Max Memory Limit', 'server-info' ),
1772 'value' => WP_MAX_MEMORY_LIMIT,
1773 );
1774
1775 $info['wp-info']['fields']['WP_DEBUG'] = array(
1776 'label' => esc_html__( 'WordPress Debugging', 'server-info' ),
1777 'value' => WP_DEBUG ? esc_html__( 'Enabled', 'server-info' ) : esc_html__( 'Disabled', 'server-info' )
1778 );
1779
1780 $cron_status = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? esc_html__( 'Disabled via wp-config.php', 'server-info' ) : esc_html__( 'Enabled', 'server-info' );
1781 $info['wp-info']['fields']['cron_status'] = array(
1782 'label' => esc_html__( 'WP-Cron Status', 'server-info' ),
1783 'value' => $cron_status,
1784 );
1785
1786 $cron_array = _get_cron_array();
1787 if ( ! empty( $cron_array ) ) {
1788 $upcoming_crons = array();
1789 $count = 0;
1790 foreach ( $cron_array as $timestamp => $cron_hooks ) {
1791 foreach ( $cron_hooks as $hook => $keys ) {
1792 if ( $count >= 3 ) break 2;
1793 $time_diff = human_time_diff( current_time( 'timestamp' ), $timestamp );
1794 /* translators: %s: Human-readable time until a cron event runs. */
1795 $upcoming_crons[ $hook ] = sprintf( esc_html__( 'In %s', 'server-info' ), $time_diff );
1796 $count++;
1797 }
1798 }
1799 if ( ! empty( $upcoming_crons ) ) {
1800 $info['wp-info']['fields']['upcoming_crons'] = array(
1801 'label' => esc_html__( 'Next 3 Cron Events', 'server-info' ),
1802 'value' => $upcoming_crons,
1803 );
1804 }
1805 }
1806
1807 return $info;
1808 }
1809
1810 /**
1811 * Renders the main plugin settings page with tabs.
1812 *
1813 * @since 0.0.1
1814 * @access public
1815 *
1816 * @return void
1817 */
1818 public static function display_server_info() {
1819 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The tab parameter only selects a read-only admin view and does not change data.
1820 $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'server';
1821 $allowed_tabs = array( 'server', 'database', 'wordpress', 'phpinfo', 'caching', 'diagnostics', 'more_plugins', 'settings', 'support' );
1822 if ( ! in_array( $active_tab, $allowed_tabs, true ) ) {
1823 $active_tab = 'server';
1824 }
1825
1826 $page_titles = array(
1827 'server' => array(
1828 'title' => esc_html__( 'Overview', 'server-info' ),
1829 'subtitle' => esc_html__( 'Real-time summary of your server health and configuration.', 'server-info' ),
1830 ),
1831 'database' => array(
1832 'title' => esc_html__( 'Database', 'server-info' ),
1833 'subtitle' => esc_html__( 'MySQL and database limits that affect performance and stability.', 'server-info' ),
1834 ),
1835 'wordpress' => array(
1836 'title' => esc_html__( 'WordPress Core', 'server-info' ),
1837 'subtitle' => esc_html__( 'Important WordPress configuration, theme, plugin, and cron details.', 'server-info' ),
1838 ),
1839 'phpinfo' => array(
1840 'title' => esc_html__( 'PHP Information', 'server-info' ),
1841 'subtitle' => esc_html__( 'Detailed PHP runtime configuration for debugging hosting issues.', 'server-info' ),
1842 ),
1843 'caching' => array(
1844 'title' => esc_html__( 'Caching', 'server-info' ),
1845 'subtitle' => esc_html__( 'Caching and optimization signals from your hosting environment.', 'server-info' ),
1846 ),
1847 'diagnostics' => array(
1848 'title' => esc_html__( 'Diagnostics & Logs', 'server-info' ),
1849 'subtitle' => esc_html__( 'Health checks and debug-log visibility for administrators.', 'server-info' ),
1850 ),
1851 'more_plugins' => array(
1852 'title' => esc_html__( 'More Plugins', 'server-info' ),
1853 'subtitle' => esc_html__( 'Other lightweight WordPress tools from the same author.', 'server-info' ),
1854 ),
1855 'settings' => array(
1856 'title' => esc_html__( 'Settings', 'server-info' ),
1857 'subtitle' => esc_html__( 'Control where Server Info appears in the WordPress admin.', 'server-info' ),
1858 ),
1859 'support' => array(
1860 'title' => esc_html__( 'Support Development', 'server-info' ),
1861 'subtitle' => esc_html__( 'Help keep Server Info maintained, tested, and free for everyone.', 'server-info' ),
1862 ),
1863 );
1864
1865 $page_title = $page_titles[ $active_tab ]['title'];
1866 $page_subtitle = $page_titles[ $active_tab ]['subtitle'];
1867 $info = self::site_info();
1868
1869 // Helper vars for top KPIs
1870 $php_version = phpversion();
1871 $memory_usage = 'N/A';
1872 if ( function_exists('memory_get_usage') ) {
1873 $memory_usage = round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB';
1874 }
1875
1876 global $wp_version, $wpdb;
1877 $db_version_raw = (string) $wpdb->db_version();
1878 $db_type = stripos( $db_version_raw, 'MariaDB' ) !== false ? 'MariaDB' : 'MySQL';
1879 preg_match( '/[0-9]+(?:\.[0-9]+)*/', $db_version_raw, $matches );
1880 $db_version = isset( $matches[0] ) ? $matches[0] : esc_html__( 'Unavailable', 'server-info' );
1881
1882 $web_server = self::get_server_value( 'SERVER_SOFTWARE', esc_html__( 'Unknown', 'server-info' ) );
1883 if (strlen($web_server) > 15) {
1884 $web_server = substr($web_server, 0, 15) . '...';
1885 }
1886
1887 // CPU Load
1888 $cpu_load = 'N/A';
1889 $cpu_pct = 0;
1890 if ( function_exists('sys_getloadavg') ) {
1891 $load = sys_getloadavg();
1892 if ( is_array($load) ) {
1893 $cpu_load = round($load[0], 2);
1894 $cpu_pct = min(100, round(($load[0] / 4) * 100)); // Rough estimate
1895 }
1896 }
1897
1898 // Memory Usage (PHP)
1899 $mem_used_bytes = memory_get_usage(true);
1900 $mem_limit_str = ini_get('memory_limit');
1901 $mem_limit_bytes = wp_convert_hr_to_bytes($mem_limit_str);
1902 if ( $mem_limit_bytes <= 0 ) {
1903 $mem_limit_bytes = $mem_used_bytes;
1904 }
1905 $mem_pct = min(100, round(($mem_used_bytes / $mem_limit_bytes) * 100));
1906
1907 // Disk Space
1908 $disk_pct = 0;
1909 if ( function_exists('disk_total_space') && function_exists('disk_free_space') ) {
1910 $disk_total = @disk_total_space( ABSPATH );
1911 $disk_free = @disk_free_space( ABSPATH );
1912 if ( $disk_total > 0 ) {
1913 $disk_used = $disk_total - $disk_free;
1914 $disk_pct = min(100, round(($disk_used / $disk_total) * 100));
1915 }
1916 }
1917
1918 $site_domain = self::get_site_domain();
1919 $registered_domain = self::get_registered_domain( $site_domain );
1920 $ssl_info = self::get_ssl_certificate_info( $site_domain );
1921 $domain_info = self::get_domain_expiry_info( $registered_domain );
1922
1923 // Calculate Overall Health Score
1924 $health_score = 100;
1925 $health_reasons = array();
1926
1927 // PHP Version check
1928 $php_v = phpversion();
1929 $php_status_label = esc_html__( 'Latest', 'server-info' );
1930 $php_status_class = 'success';
1931 if ( version_compare( $php_v, '7.4', '<' ) ) {
1932 $health_score -= 30;
1933 $health_reasons[] = "Critical: PHP version is very outdated ($php_v).";
1934 $php_status_label = esc_html__( 'Critical', 'server-info' );
1935 $php_status_class = 'neutral';
1936 } elseif ( version_compare( $php_v, '8.3', '<' ) ) {
1937 $health_score -= 10;
1938 $health_reasons[] = "Warning: PHP version ($php_v) is below recommended 8.3.";
1939 $php_status_label = esc_html__( 'Update recommended', 'server-info' );
1940 $php_status_class = 'neutral';
1941 }
1942
1943 // Memory Limit Check
1944 $mem_limit_int = intval( $mem_limit_str );
1945 if ( false !== strpos( $mem_limit_str, 'G' ) ) {
1946 $mem_limit_int *= 1024;
1947 }
1948 if ( $mem_limit_int > 0 && $mem_limit_int < 256 ) {
1949 $health_score -= 10;
1950 $health_reasons[] = "Warning: Low PHP memory limit ($mem_limit_str).";
1951 }
1952
1953 // WordPress Core Check
1954 global $wp_version;
1955 $core_updates = get_site_transient('update_core');
1956 $wp_core_status_label = esc_html__( 'Up to date', 'server-info' );
1957 $wp_core_status_class = 'success';
1958 if ( isset( $core_updates->updates ) && is_array( $core_updates->updates ) ) {
1959 foreach ( $core_updates->updates as $update ) {
1960 if ( $update->response === 'upgrade' ) {
1961 $health_score -= 10;
1962 $health_reasons[] = "Warning: WordPress core is outdated.";
1963 $wp_core_status_label = esc_html__( 'Update available', 'server-info' );
1964 $wp_core_status_class = 'neutral';
1965 break;
1966 }
1967 }
1968 }
1969
1970 // Security Check
1971 $wp_config_path = ABSPATH . 'wp-config.php';
1972 if ( file_exists( $wp_config_path ) && wp_is_writable( $wp_config_path ) ) {
1973 $health_score -= 10;
1974 $health_reasons[] = "Security: wp-config.php is writable.";
1975 }
1976
1977 // Resource Checks
1978 if ( $mem_pct > 90 ) {
1979 $health_score -= 5;
1980 $health_reasons[] = "Warning: High memory usage ($mem_pct%).";
1981 }
1982 if ( $disk_pct > 90 ) {
1983 $health_score -= 5;
1984 $health_reasons[] = "Warning: High disk usage ($disk_pct%).";
1985 }
1986
1987 if ( ! empty( $ssl_info['available'] ) && 'danger' === $ssl_info['status_key'] ) {
1988 $health_score -= 15;
1989 $health_reasons[] = 'Security: SSL/TLS certificate needs attention.';
1990 } elseif ( ! empty( $ssl_info['available'] ) && 'warning' === $ssl_info['status_key'] ) {
1991 $health_score -= 8;
1992 $health_reasons[] = 'Warning: SSL/TLS certificate expires soon.';
1993 }
1994
1995 if ( ! empty( $domain_info['available'] ) && 'danger' === $domain_info['status_key'] ) {
1996 $health_score -= 15;
1997 $health_reasons[] = 'Critical: Domain registration appears expired.';
1998 } elseif ( ! empty( $domain_info['available'] ) && 'warning' === $domain_info['status_key'] ) {
1999 $health_score -= 8;
2000 $health_reasons[] = 'Warning: Domain registration expires soon.';
2001 }
2002
2003 $health_score = max(0, $health_score);
2004
2005 // Determine Status
2006 if ( $health_score >= 90 ) {
2007 $health_status = 'Excellent';
2008 $health_color = '#10b981'; // Green
2009 $health_bg = '#d1fae5';
2010 $health_msg = 'Your server is running smoothly.';
2011 } elseif ( $health_score >= 70 ) {
2012 $health_status = 'Fair';
2013 $health_color = '#f59e0b'; // Amber
2014 $health_bg = '#fef3c7';
2015 $health_msg = 'Needs attention: ' . (isset($health_reasons[0]) ? $health_reasons[0] : 'Suboptimal settings.');
2016 } else {
2017 $health_status = 'Critical';
2018 $health_color = '#ef4444'; // Red
2019 $health_bg = '#fee2e2';
2020 $health_msg = 'Urgent: ' . (isset($health_reasons[0]) ? $health_reasons[0] : 'Multiple severe issues.');
2021 }
2022 ?>
2023 <div class="wrap"><h1 style="display:none;"></h1></div>
2024 <div class="server-info-wrapper">
2025
2026 <div class="si-sidebar">
2027 <div class="si-brand">
2028 <div class="si-brand-icon"><span class="dashicons dashicons-networking"></span></div>
2029 <div class="si-brand-text">
2030 <h2>Server Info <span class="si-version-tag">v<?php echo esc_html( SERVER_INFO_PLUGIN_VERSION ); ?></span></h2>
2031 <p class="si-brand-sub">System Health & Diagnostics</p>
2032 </div>
2033 </div>
2034
2035 <div class="si-nav">
2036 <a href="?page=server_info_display&tab=server" class="si-nav-item <?php echo 'server' === $active_tab ? 'active' : ''; ?>">
2037 <span class="dashicons dashicons-admin-home"></span> <?php esc_html_e( 'Overview', 'server-info' ); ?>
2038 </a>
2039 <a href="?page=server_info_display&tab=database" class="si-nav-item <?php echo 'database' === $active_tab ? 'active' : ''; ?>">
2040 <span class="dashicons dashicons-database"></span> <?php esc_html_e( 'Database', 'server-info' ); ?>
2041 </a>
2042 <a href="?page=server_info_display&tab=wordpress" class="si-nav-item <?php echo 'wordpress' === $active_tab ? 'active' : ''; ?>">
2043 <span class="dashicons dashicons-wordpress"></span> <?php esc_html_e( 'WordPress Core', 'server-info' ); ?>
2044 </a>
2045 <a href="?page=server_info_display&tab=phpinfo" class="si-nav-item <?php echo 'phpinfo' === $active_tab ? 'active' : ''; ?>">
2046 <span class="dashicons dashicons-editor-code"></span> <?php esc_html_e( 'PHP Information', 'server-info' ); ?>
2047 </a>
2048 <a href="?page=server_info_display&tab=caching" class="si-nav-item <?php echo 'caching' === $active_tab ? 'active' : ''; ?>">
2049 <span class="dashicons dashicons-dashboard"></span> <?php esc_html_e( 'Caching', 'server-info' ); ?>
2050 </a>
2051 <a href="?page=server_info_display&tab=diagnostics" class="si-nav-item <?php echo 'diagnostics' === $active_tab ? 'active' : ''; ?>">
2052 <span class="dashicons dashicons-admin-tools"></span> <?php esc_html_e( 'Diagnostics & Logs', 'server-info' ); ?>
2053 </a>
2054 <a href="?page=server_info_display&tab=more_plugins" class="si-nav-item <?php echo 'more_plugins' === $active_tab ? 'active' : ''; ?>">
2055 <span class="dashicons dashicons-admin-plugins"></span> <?php esc_html_e( 'More Plugins', 'server-info' ); ?>
2056 </a>
2057 <a href="?page=server_info_display&tab=settings" class="si-nav-item <?php echo 'settings' === $active_tab ? 'active' : ''; ?>">
2058 <span class="dashicons dashicons-admin-settings"></span> <?php esc_html_e( 'Settings', 'server-info' ); ?>
2059 </a>
2060 <a href="?page=server_info_display&tab=support" class="si-nav-item <?php echo 'support' === $active_tab ? 'active' : ''; ?>">
2061 <span class="dashicons dashicons-heart"></span> <?php esc_html_e( 'Support', 'server-info' ); ?>
2062 </a>
2063 </div>
2064
2065 <div class="si-health-widget">
2066 <h3 class="si-health-title">Overall Health</h3>
2067 <p class="si-health-score" style="color: <?php echo esc_attr($health_color); ?>"><?php echo esc_html($health_score); ?>%</p>
2068 <p class="si-health-status" style="color: <?php echo esc_attr($health_color); ?>"><?php echo esc_html($health_status); ?></p>
2069 <div class="si-health-chart">
2070 <svg viewBox="0 0 100 30" preserveAspectRatio="none">
2071 <path d="M0,20 C15,20 15,5 30,5 C45,5 45,25 60,25 C75,25 75,10 90,10 C95,10 98,15 100,20 L100,30 L0,30 Z" fill="<?php echo esc_attr($health_bg); ?>" />
2072 <path d="M0,20 C15,20 15,5 30,5 C45,5 45,25 60,25 C75,25 75,10 90,10 C95,10 98,15 100,20" fill="none" stroke="<?php echo esc_attr($health_color); ?>" stroke-width="2" />
2073 </svg>
2074 </div>
2075 <p class="si-health-desc"><?php echo esc_html($health_msg); ?></p>
2076 <a href="?page=server_info_display&tab=diagnostics" class="si-btn-outline">View Health Details &rarr;</a>
2077 </div>
2078
2079 <div class="si-rating-widget">
2080 <div class="si-rating-stars">
2081 <span class="dashicons dashicons-star-filled"></span>
2082 <span class="dashicons dashicons-star-filled"></span>
2083 <span class="dashicons dashicons-star-filled"></span>
2084 <span class="dashicons dashicons-star-filled"></span>
2085 <span class="dashicons dashicons-star-filled"></span>
2086 </div>
2087 <p class="si-rating-title">Love Server Info?</p>
2088 <p class="si-rating-desc">Please share your experience with the plugin.</p>
2089 <a href="https://wordpress.org/support/plugin/server-info/reviews/#new-post" target="_blank" rel="noopener noreferrer" class="si-btn-outline si-rating-btn">Leave a Rating &rarr;</a>
2090 </div>
2091
2092 <div class="si-support-widget">
2093 <p class="si-support-title"><?php esc_html_e( 'Support the Author', 'server-info' ); ?></p>
2094 <p class="si-support-desc"><?php esc_html_e( 'A small contribution helps maintain compatibility, testing, and new diagnostics.', 'server-info' ); ?></p>
2095 <a href="?page=server_info_display&tab=support" class="si-btn-primary si-support-btn"><?php esc_html_e( 'Support Development', 'server-info' ); ?></a>
2096 </div>
2097 </div>
2098
2099 <div class="si-main">
2100 <div class="si-topbar">
2101 <div class="si-page-title">
2102 <h1><?php echo esc_html( $page_title ); ?></h1>
2103 <p><?php echo esc_html( $page_subtitle ); ?></p>
2104 </div>
2105 <div class="si-actions">
2106 <a href="<?php echo esc_url( add_query_arg( array( 'page' => 'server_info_display', 'tab' => $active_tab ), admin_url( 'options-general.php' ) ) ); ?>" class="si-btn-secondary">
2107 <span class="dashicons dashicons-update"></span> <?php esc_html_e( 'Refresh Data', 'server-info' ); ?>
2108 </a>
2109 </div>
2110 </div>
2111
2112 <?php if ( 'server' === $active_tab ) : ?>
2113
2114 <div class="si-kpi-grid">
2115 <div class="si-kpi-card <?php echo $health_score >= 80 ? 'status-excellent' : 'status-primary'; ?>" style="--si-success: <?php echo esc_attr($health_color); ?>;">
2116 <div class="si-kpi-icon" style="background: <?php echo esc_attr($health_color); ?>20; color: <?php echo esc_attr($health_color); ?>;"><span class="dashicons dashicons-chart-line"></span></div>
2117 <div class="si-kpi-content">
2118 <div class="si-kpi-label">Server Health</div>
2119 <div class="si-kpi-value"><?php echo esc_html($health_score); ?>%</div>
2120 <div class="si-kpi-status" style="color: <?php echo esc_attr($health_color); ?>;"><?php echo esc_html($health_status); ?></div>
2121 </div>
2122 </div>
2123 <div class="si-kpi-card status-primary">
2124 <div class="si-kpi-icon purple"><span class="dashicons dashicons-editor-code"></span></div>
2125 <div class="si-kpi-content">
2126 <div class="si-kpi-label">PHP Version</div>
2127 <div class="si-kpi-value"><?php echo esc_html($php_version); ?></div>
2128 <div class="si-kpi-status <?php echo esc_attr( $php_status_class ); ?>"><?php echo esc_html( $php_status_label ); ?></div>
2129 </div>
2130 </div>
2131 <div class="si-kpi-card status-primary">
2132 <div class="si-kpi-icon blue"><span class="dashicons dashicons-dashboard"></span></div>
2133 <div class="si-kpi-content">
2134 <div class="si-kpi-label">Memory Usage</div>
2135 <div class="si-kpi-value"><?php echo esc_html($memory_usage); ?></div>
2136 <div class="si-kpi-status neutral">Used / Limit</div>
2137 </div>
2138 </div>
2139 <div class="si-kpi-card status-excellent">
2140 <div class="si-kpi-icon green"><span class="dashicons dashicons-database"></span></div>
2141 <div class="si-kpi-content">
2142 <div class="si-kpi-label">Database</div>
2143 <div class="si-kpi-value"><?php echo esc_html( $db_type . ' ' . $db_version ); ?></div>
2144 <div class="si-kpi-status success">Healthy</div>
2145 </div>
2146 </div>
2147 <div class="si-kpi-card status-primary">
2148 <div class="si-kpi-icon orange"><span class="dashicons dashicons-wordpress"></span></div>
2149 <div class="si-kpi-content">
2150 <div class="si-kpi-label">WordPress</div>
2151 <div class="si-kpi-value"><?php echo esc_html($wp_version); ?></div>
2152 <div class="si-kpi-status <?php echo esc_attr( $wp_core_status_class ); ?>"><?php echo esc_html( $wp_core_status_label ); ?></div>
2153 </div>
2154 </div>
2155 </div>
2156
2157 <div class="si-section si-basic-section">
2158 <div class="si-section-header">
2159 <h3 class="si-section-title"><?php esc_html_e( 'Basic Information', 'server-info' ); ?></h3>
2160 </div>
2161 <div class="si-basic-grid">
2162 <div class="si-basic-card">
2163 <div class="si-check-heading">
2164 <span class="dashicons dashicons-networking"></span>
2165 <strong><?php esc_html_e( 'Hosting', 'server-info' ); ?></strong>
2166 </div>
2167 <ul class="si-mini-list">
2168 <?php
2169 $fields = array( 'server_hostname', 'server_ip', 'server_protocol' );
2170 foreach ( $fields as $key ) {
2171 if ( isset( $info['wp-server']['fields'][ $key ] ) ) {
2172 $field = $info['wp-server']['fields'][ $key ];
2173 ?>
2174 <li>
2175 <span><?php echo esc_html( $field['label'] ); ?></span>
2176 <strong><?php echo esc_html( $field['value'] ); ?></strong>
2177 </li>
2178 <?php
2179 }
2180 }
2181 ?>
2182 </ul>
2183 </div>
2184
2185 <div class="si-basic-card si-check-card <?php echo esc_attr( 'status-' . $ssl_info['status_key'] ); ?>">
2186 <div class="si-check-heading">
2187 <span class="dashicons dashicons-lock"></span>
2188 <strong><?php esc_html_e( 'SSL/TLS Certificate', 'server-info' ); ?></strong>
2189 <span class="si-status-pill"><?php echo esc_html( $ssl_info['status'] ); ?></span>
2190 </div>
2191 <ul class="si-mini-list">
2192 <li><span><?php esc_html_e( 'Issued by', 'server-info' ); ?></span><strong><?php echo esc_html( $ssl_info['issuer'] ); ?></strong></li>
2193 <li><span><?php esc_html_e( 'Domain', 'server-info' ); ?></span><strong><?php echo esc_html( $ssl_info['domain'] ); ?></strong></li>
2194 <li><span><?php esc_html_e( 'Expiry', 'server-info' ); ?></span><strong><?php echo esc_html( $ssl_info['expiry'] ); ?></strong></li>
2195 <li>
2196 <span><?php esc_html_e( 'Days left', 'server-info' ); ?></span>
2197 <strong>
2198 <?php
2199 if ( null === $ssl_info['days_left'] ) {
2200 esc_html_e( 'Unavailable', 'server-info' );
2201 } elseif ( $ssl_info['days_left'] < 0 ) {
2202 /* translators: %s: Number of days since the certificate expired. */
2203 printf( esc_html__( '%s days ago', 'server-info' ), esc_html( absint( $ssl_info['days_left'] ) ) );
2204 } else {
2205 /* translators: %s: Number of days until the certificate expires. */
2206 printf( esc_html__( '%s days', 'server-info' ), esc_html( absint( $ssl_info['days_left'] ) ) );
2207 }
2208 ?>
2209 </strong>
2210 </li>
2211 </ul>
2212 </div>
2213
2214 <div class="si-basic-card si-check-card <?php echo esc_attr( 'status-' . $domain_info['status_key'] ); ?>">
2215 <div class="si-check-heading">
2216 <span class="dashicons dashicons-admin-site-alt3"></span>
2217 <strong><?php esc_html_e( 'Domain Expiry', 'server-info' ); ?></strong>
2218 <span class="si-status-pill"><?php echo esc_html( $domain_info['status'] ); ?></span>
2219 </div>
2220 <ul class="si-mini-list">
2221 <li><span><?php esc_html_e( 'Registrar', 'server-info' ); ?></span><strong><?php echo esc_html( $domain_info['registrar'] ); ?></strong></li>
2222 <li><span><?php esc_html_e( 'Domain', 'server-info' ); ?></span><strong><?php echo esc_html( $domain_info['domain'] ); ?></strong></li>
2223 <li><span><?php esc_html_e( 'Expiry date', 'server-info' ); ?></span><strong><?php echo esc_html( $domain_info['expiry'] ); ?></strong></li>
2224 <li>
2225 <span><?php esc_html_e( 'Days left', 'server-info' ); ?></span>
2226 <strong>
2227 <?php
2228 if ( null === $domain_info['days_left'] ) {
2229 esc_html_e( 'Unavailable', 'server-info' );
2230 } elseif ( $domain_info['days_left'] < 0 ) {
2231 /* translators: %s: Number of days since the domain registration expired. */
2232 printf( esc_html__( '%s days ago', 'server-info' ), esc_html( absint( $domain_info['days_left'] ) ) );
2233 } else {
2234 /* translators: %s: Number of days until the domain registration expires. */
2235 printf( esc_html__( '%s days', 'server-info' ), esc_html( absint( $domain_info['days_left'] ) ) );
2236 }
2237 ?>
2238 </strong>
2239 </li>
2240 </ul>
2241 </div>
2242 </div>
2243 </div>
2244
2245 <div class="si-dashboard-grid">
2246
2247 <div class="si-section">
2248 <div class="si-section-header">
2249 <h3 class="si-section-title">System Resources</h3>
2250 </div>
2251 <div class="si-resources-circles">
2252 <div class="si-circle-wrapper">
2253 <div class="si-circle-chart">
2254 <svg viewBox="0 0 100 100">
2255 <circle class="si-circle-bg" cx="50" cy="50" r="40"></circle>
2256 <circle class="si-circle-progress" cx="50" cy="50" r="40" stroke-dasharray="251" stroke-dashoffset="<?php echo esc_attr(251 - (251 * $cpu_pct / 100)); ?>"></circle>
2257 </svg>
2258 <div class="si-circle-val"><?php echo esc_html($cpu_load); ?></div>
2259 <div class="si-circle-label">Load</div>
2260 </div>
2261 <div class="si-circle-title">CPU Load</div>
2262 </div>
2263 <div class="si-circle-wrapper">
2264 <div class="si-circle-chart">
2265 <svg viewBox="0 0 100 100">
2266 <circle class="si-circle-bg" cx="50" cy="50" r="40"></circle>
2267 <circle class="si-circle-progress blue" cx="50" cy="50" r="40" stroke-dasharray="251" stroke-dashoffset="<?php echo esc_attr(251 - (251 * $mem_pct / 100)); ?>"></circle>
2268 </svg>
2269 <div class="si-circle-val"><?php echo esc_html($mem_pct); ?>%</div>
2270 <div class="si-circle-label">Used</div>
2271 </div>
2272 <div class="si-circle-title">Memory</div>
2273 </div>
2274 <div class="si-circle-wrapper">
2275 <div class="si-circle-chart">
2276 <svg viewBox="0 0 100 100">
2277 <circle class="si-circle-bg" cx="50" cy="50" r="40"></circle>
2278 <circle class="si-circle-progress" cx="50" cy="50" r="40" stroke-dasharray="251" stroke-dashoffset="<?php echo esc_attr(251 - (251 * $disk_pct / 100)); ?>"></circle>
2279 </svg>
2280 <div class="si-circle-val"><?php echo esc_html($disk_pct); ?>%</div>
2281 <div class="si-circle-label">Used</div>
2282 </div>
2283 <div class="si-circle-title">Disk Space</div>
2284 </div>
2285 </div>
2286 <div class="si-memory-bars">
2287 <div class="si-mem-stat">
2288 <span class="si-mem-label"><span class="dashicons dashicons-minus"></span> Total Memory</span>
2289 <span class="si-mem-val"><?php echo esc_html( ini_get( 'memory_limit' ) ); ?></span>
2290 </div>
2291 <div class="si-mem-stat">
2292 <span class="si-mem-label"><span class="dashicons dashicons-minus" style="color:var(--si-primary);"></span> Used Memory</span>
2293 <span class="si-mem-val"><?php echo esc_html($memory_usage); ?></span>
2294 </div>
2295 </div>
2296 <div class="si-section-footer">
2297 <a href="?page=server_info_display&tab=diagnostics">View Performance Details &rarr;</a>
2298 </div>
2299 </div>
2300
2301 <div class="si-section">
2302 <div class="si-section-header">
2303 <h3 class="si-section-title">PHP Configuration</h3>
2304 </div>
2305 <?php
2306 $max_execution_time = ini_get( 'max_execution_time' );
2307 $max_input_vars = ini_get( 'max_input_vars' );
2308 $upload_max_size = ini_get( 'upload_max_filesize' );
2309 $post_max_size = ini_get( 'post_max_size' );
2310 $loaded_extensions = function_exists( 'get_loaded_extensions' ) ? get_loaded_extensions() : array();
2311 $opcache_status = esc_html__( 'Unavailable', 'server-info' );
2312
2313 if ( function_exists( 'opcache_get_status' ) ) {
2314 $opcache = @opcache_get_status( false );
2315 $opcache_status = ( is_array( $opcache ) && ! empty( $opcache['opcache_enabled'] ) ) ? esc_html__( 'Enabled', 'server-info' ) : esc_html__( 'Disabled', 'server-info' );
2316 } elseif ( extension_loaded( 'Zend OPcache' ) || extension_loaded( 'opcache' ) ) {
2317 $opcache_status = esc_html__( 'Loaded', 'server-info' );
2318 }
2319
2320 $php_overview_items = array(
2321 array(
2322 'icon' => 'dashicons-media-code',
2323 'label' => esc_html__( 'PHP Version', 'server-info' ),
2324 'value' => $php_version,
2325 'hint' => $php_status_label,
2326 ),
2327 array(
2328 'icon' => 'dashicons-dashboard',
2329 'label' => esc_html__( 'Memory Limit', 'server-info' ),
2330 'value' => ini_get( 'memory_limit' ),
2331 'hint' => esc_html__( 'Per PHP process', 'server-info' ),
2332 ),
2333 array(
2334 'icon' => 'dashicons-clock',
2335 'label' => esc_html__( 'Max Execution', 'server-info' ),
2336 /* translators: %s: Maximum PHP execution time in seconds. */
2337 'value' => '' === $max_execution_time ? esc_html__( 'Unavailable', 'server-info' ) : sprintf( esc_html__( '%s sec', 'server-info' ), $max_execution_time ),
2338 'hint' => esc_html__( 'Script timeout', 'server-info' ),
2339 ),
2340 array(
2341 'icon' => 'dashicons-upload',
2342 'label' => esc_html__( 'Upload Max', 'server-info' ),
2343 'value' => $upload_max_size ? $upload_max_size : esc_html__( 'Unavailable', 'server-info' ),
2344 'hint' => esc_html__( 'File upload limit', 'server-info' ),
2345 ),
2346 array(
2347 'icon' => 'dashicons-forms',
2348 'label' => esc_html__( 'Post Max Size', 'server-info' ),
2349 'value' => $post_max_size ? $post_max_size : esc_html__( 'Unavailable', 'server-info' ),
2350 'hint' => esc_html__( 'Request body limit', 'server-info' ),
2351 ),
2352 array(
2353 'icon' => 'dashicons-editor-code',
2354 'label' => esc_html__( 'PHP SAPI', 'server-info' ),
2355 'value' => function_exists( 'php_sapi_name' ) ? php_sapi_name() : esc_html__( 'Unavailable', 'server-info' ),
2356 'hint' => esc_html__( 'Runtime interface', 'server-info' ),
2357 ),
2358 array(
2359 'icon' => 'dashicons-performance',
2360 'label' => esc_html__( 'OPcache', 'server-info' ),
2361 'value' => $opcache_status,
2362 'hint' => esc_html__( 'Bytecode cache', 'server-info' ),
2363 ),
2364 array(
2365 'icon' => 'dashicons-admin-plugins',
2366 'label' => esc_html__( 'Extensions', 'server-info' ),
2367 'value' => count( $loaded_extensions ),
2368 'hint' => esc_html__( 'Loaded modules', 'server-info' ),
2369 ),
2370 array(
2371 'icon' => 'dashicons-list-view',
2372 'label' => esc_html__( 'Max Input Vars', 'server-info' ),
2373 'value' => $max_input_vars ? $max_input_vars : esc_html__( 'Unavailable', 'server-info' ),
2374 'hint' => esc_html__( 'Form fields limit', 'server-info' ),
2375 ),
2376 );
2377 ?>
2378 <div class="si-data-grid si-php-overview-grid">
2379 <?php foreach ( $php_overview_items as $item ) : ?>
2380 <div class="si-data-item si-php-data-item">
2381 <span class="si-data-item-label"><span class="dashicons <?php echo esc_attr( $item['icon'] ); ?>"></span> <?php echo esc_html( $item['label'] ); ?></span>
2382 <span class="si-data-item-val"><?php echo esc_html( $item['value'] ); ?></span>
2383 <span class="si-data-item-hint"><?php echo esc_html( $item['hint'] ); ?></span>
2384 </div>
2385 <?php endforeach; ?>
2386 </div>
2387 <div class="si-section-footer" style="margin-top: 30px;">
2388 <a href="?page=server_info_display&tab=phpinfo">View All PHP Settings &rarr;</a>
2389 </div>
2390 </div>
2391
2392 <div class="si-section" style="background:transparent; border:none; box-shadow:none; padding:0;">
2393 <div class="si-section-header">
2394 <h3 class="si-section-title">Quick Actions</h3>
2395 </div>
2396 <div class="si-actions-grid">
2397 <a href="?page=server_info_display&tab=diagnostics" class="si-action-card">
2398 <div class="si-action-icon"><span class="dashicons dashicons-analytics"></span></div>
2399 <div class="si-action-texts">
2400 <span class="si-action-title">View Logs</span>
2401 <span class="si-action-desc">Access error logs</span>
2402 </div>
2403 </a>
2404 <a href="?page=server_info_display&tab=phpinfo" class="si-action-card">
2405 <div class="si-action-icon"><span class="dashicons dashicons-editor-code"></span></div>
2406 <div class="si-action-texts">
2407 <span class="si-action-title">PHP Info</span>
2408 <span class="si-action-desc">View runtime settings</span>
2409 </div>
2410 </a>
2411 </div>
2412 </div>
2413 </div>
2414 <?php else:
2415 // Fallback for other tabs (Database, WP Core, Diagnostics, Plugins)
2416 if ( 'diagnostics' === $active_tab ) {
2417 self::display_diagnostics_tab();
2418 } elseif ( 'phpinfo' === $active_tab ) {
2419 self::display_phpinfo_tab();
2420 } elseif ( 'more_plugins' === $active_tab ) {
2421 self::display_more_plugins_tab();
2422 } elseif ( 'settings' === $active_tab ) {
2423 self::display_settings_tab();
2424 } elseif ( 'support' === $active_tab ) {
2425 self::display_support_tab();
2426 } else {
2427 $group_key = 'wp-server';
2428 if ( 'database' === $active_tab ) {
2429 $group_key = 'wp-database';
2430 } elseif ( 'wordpress' === $active_tab ) {
2431 $group_key = 'wp-info';
2432 } elseif ( 'caching' === $active_tab ) {
2433 $group_key = 'wp-caching';
2434 }
2435
2436 if ( isset( $info[ $group_key ] ) && ! empty( $info[ $group_key ]['fields'] ) ) {
2437 $details = $info[ $group_key ];
2438 echo '<div class="si-section">';
2439 echo '<div class="si-section-header"><h3 class="si-section-title">' . esc_html( $details['label'] ) . '</h3></div>';
2440 echo '<ul class="si-table-list">';
2441 foreach ( $details['fields'] as $field ) {
2442 $value_html = '';
2443 if ( is_array( $field['value'] ) ) {
2444 $value_html .= '<div class="si-array-val">';
2445 foreach ( $field['value'] as $name => $val ) {
2446 if ( empty( $val ) ) {
2447 $value_html .= sprintf( '<div>%s</div>', esc_html( $name ) );
2448 } else {
2449 $value_html .= sprintf( '<div><strong>%s</strong> %s</div>', esc_html( $name ), esc_html( $val ) );
2450 }
2451 }
2452 $value_html .= '</div>';
2453 } else {
2454 $value_html = esc_html( $field['value'] );
2455 }
2456 ?>
2457 <li class="si-table-row <?php echo is_array( $field['value'] ) ? 'has-array' : ''; ?>">
2458 <span class="si-table-label"><span class="dashicons dashicons-arrow-right-alt2"></span> <?php echo esc_html( $field['label'] ); ?></span>
2459 <span class="si-table-val"><?php echo wp_kses_post( $value_html ); ?></span>
2460 </li>
2461 <?php
2462 }
2463 echo '</ul></div>';
2464 }
2465 }
2466 endif; ?>
2467
2468 </div>
2469 <?php self::display_support_floating_checkout(); ?>
2470 </div>
2471 <?php
2472 }
2473
2474 /**
2475 * Displays the more plugins tab content for cross-promotion.
2476 *
2477 * @since 0.0.1
2478 * @access public
2479 *
2480 * @return void
2481 */
2482 public static function display_more_plugins_tab() {
2483 if ( ! function_exists( 'is_plugin_active' ) ) {
2484 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2485 }
2486
2487 $plugins = array(
2488 array(
2489 'name' => esc_html__( 'Advance Canonical URL', 'server-info' ),
2490 'desc' => esc_html__( 'Easily manage and customize canonical URLs to eliminate duplicate content issues and boost your SEO rankings.', 'server-info' ),
2491 'icon' => 'dashicons-admin-links',
2492 'slug' => 'advance-canonical-url',
2493 'file' => 'advance-canonical-url/functions.php',
2494 ),
2495 array(
2496 'name' => esc_html__( 'Metaviewer - Debug Meta Data', 'server-info' ),
2497 'desc' => esc_html__( 'The ultimate developer tool to instantly view, inspect, and debug post, user, and term meta data directly from the frontend.', 'server-info' ),
2498 'icon' => 'dashicons-visibility',
2499 'slug' => 'metaviewer-debug-meta-data',
2500 'file' => 'metaviewer-debug-meta-data/metaviewer.php',
2501 ),
2502 array(
2503 'name' => esc_html__( 'Randomize Password', 'server-info' ),
2504 'desc' => esc_html__( 'Enhance your security by forcing highly secure, completely randomized passwords for user accounts upon creation or reset.', 'server-info' ),
2505 'icon' => 'dashicons-lock',
2506 'slug' => 'randomize-password',
2507 'file' => 'randomize-password/randomize-password.php',
2508 ),
2509 array(
2510 'name' => esc_html__( 'Fusion Pricing Tables', 'server-info' ),
2511 'desc' => esc_html__( 'Build flexible Elementor pricing tables with 15 ready-made skins, billing toggles, ribbons, and responsive controls.', 'server-info' ),
2512 'icon' => 'dashicons-editor-table',
2513 'slug' => 'fusion-pricing-tables',
2514 'file' => 'fusion-pricing-tables/pricing-grid.php',
2515 ),
2516 );
2517 ?>
2518 <div class="si-dashboard-grid si-plugins-grid">
2519 <?php foreach ( $plugins as $plugin ) :
2520 $is_installed = file_exists( WP_PLUGIN_DIR . '/' . $plugin['file'] );
2521 $is_active = $is_installed && is_plugin_active( $plugin['file'] );
2522 ?>
2523 <div class="si-card">
2524 <div class="si-plugin-card-header">
2525 <div class="si-plugin-card-icon">
2526 <span class="dashicons <?php echo esc_attr( $plugin['icon'] ); ?>"></span>
2527 </div>
2528 <h3 class="si-plugin-card-title"><?php echo esc_html( $plugin['name'] ); ?></h3>
2529 </div>
2530 <p class="si-plugin-card-desc">
2531 <?php echo esc_html( $plugin['desc'] ); ?>
2532 </p>
2533 <div>
2534 <?php if ( $is_active ) : ?>
2535 <button type="button" class="si-btn-active" disabled><?php esc_html_e( 'Active', 'server-info' ); ?></button>
2536 <?php elseif ( $is_installed ) :
2537 $activate_url = wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin['file'] ) ), 'activate-plugin_' . $plugin['file'] );
2538 ?>
2539 <a href="<?php echo esc_url( $activate_url ); ?>" class="si-btn-activate"><?php esc_html_e( 'Activate', 'server-info' ); ?></a>
2540 <?php else :
2541 $install_url = wp_nonce_url( admin_url( 'update.php?action=install-plugin&plugin=' . urlencode( $plugin['slug'] ) ), 'install-plugin_' . $plugin['slug'] );
2542 ?>
2543 <a href="<?php echo esc_url( $install_url ); ?>" class="si-btn-install"><?php esc_html_e( 'Install Now', 'server-info' ); ?></a>
2544 <?php endif; ?>
2545 </div>
2546 </div>
2547 <?php endforeach; ?>
2548 </div>
2549 <?php
2550 }
2551
2552 /**
2553 * Displays the plugin settings tab.
2554 *
2555 * @return void
2556 */
2557 public static function display_settings_tab() {
2558 $options = self::get_options();
2559 $schemes = self::get_appearance_schemes();
2560 ?>
2561 <div class="si-dashboard-grid si-settings-grid">
2562 <div class="si-section">
2563 <div class="si-section-header">
2564 <h3 class="si-section-title"><?php esc_html_e( 'Admin Display Settings', 'server-info' ); ?></h3>
2565 </div>
2566 <form method="post" action="options.php" class="si-settings-form">
2567 <?php settings_fields( 'server_info_settings' ); ?>
2568 <label class="si-toggle-row" for="server-info-admin-bar-hud">
2569 <span>
2570 <strong><?php esc_html_e( 'Admin Bar HUD', 'server-info' ); ?></strong>
2571 <small><?php esc_html_e( 'Show environment, PHP version, and memory usage in the WordPress admin bar.', 'server-info' ); ?></small>
2572 </span>
2573 <span class="si-toggle-control">
2574 <input type="checkbox" id="server-info-admin-bar-hud" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[admin_bar_hud]" value="1" <?php checked( ! empty( $options['admin_bar_hud'] ) ); ?> />
2575 <span class="si-toggle-switch" aria-hidden="true"></span>
2576 </span>
2577 </label>
2578 <label class="si-toggle-row" for="server-info-footer-info">
2579 <span>
2580 <strong><?php esc_html_e( 'Admin Footer Diagnostics', 'server-info' ); ?></strong>
2581 <small><?php esc_html_e( 'Show a compact environment and memory summary in the admin footer.', 'server-info' ); ?></small>
2582 </span>
2583 <span class="si-toggle-control">
2584 <input type="checkbox" id="server-info-footer-info" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[footer_info]" value="1" <?php checked( ! empty( $options['footer_info'] ) ); ?> />
2585 <span class="si-toggle-switch" aria-hidden="true"></span>
2586 </span>
2587 </label>
2588 <label class="si-toggle-row" for="server-info-domain-expiry-lookup">
2589 <span>
2590 <strong><?php esc_html_e( 'Domain Expiry Lookup', 'server-info' ); ?></strong>
2591 <small><?php esc_html_e( 'Check public RDAP or WHOIS registration data for the site domain and cache the result to keep Overview fast.', 'server-info' ); ?></small>
2592 </span>
2593 <span class="si-toggle-control">
2594 <input type="checkbox" id="server-info-domain-expiry-lookup" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[domain_expiry_lookup]" value="1" <?php checked( ! empty( $options['domain_expiry_lookup'] ) ); ?> />
2595 <span class="si-toggle-switch" aria-hidden="true"></span>
2596 </span>
2597 </label>
2598
2599 <div class="si-settings-subsection">
2600 <h4><?php esc_html_e( 'Appearance', 'server-info' ); ?></h4>
2601 <p><?php esc_html_e( 'Choose a layout color preset or set a custom background and text color for the Server Info admin screen.', 'server-info' ); ?></p>
2602 <div class="si-appearance-options">
2603 <?php foreach ( $schemes as $scheme_key => $scheme ) : ?>
2604 <label class="si-appearance-option" for="server-info-appearance-<?php echo esc_attr( $scheme_key ); ?>">
2605 <input type="radio" id="server-info-appearance-<?php echo esc_attr( $scheme_key ); ?>" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[appearance_scheme]" value="<?php echo esc_attr( $scheme_key ); ?>" <?php checked( $options['appearance_scheme'], $scheme_key ); ?> />
2606 <span><?php echo esc_html( $scheme['label'] ); ?></span>
2607 </label>
2608 <?php endforeach; ?>
2609 </div>
2610 <div class="si-color-controls">
2611 <label for="server-info-custom-bg-color">
2612 <span><?php esc_html_e( 'Background', 'server-info' ); ?></span>
2613 <input type="color" id="server-info-custom-bg-color" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[custom_bg_color]" value="<?php echo esc_attr( $options['custom_bg_color'] ); ?>" />
2614 </label>
2615 <label for="server-info-custom-text-color">
2616 <span><?php esc_html_e( 'Text', 'server-info' ); ?></span>
2617 <input type="color" id="server-info-custom-text-color" name="<?php echo esc_attr( SERVER_INFO_OPTION_NAME ); ?>[custom_text_color]" value="<?php echo esc_attr( $options['custom_text_color'] ); ?>" />
2618 </label>
2619 </div>
2620 </div>
2621 <?php submit_button( esc_html__( 'Save Settings', 'server-info' ), 'primary si-submit', 'submit', false ); ?>
2622 </form>
2623 </div>
2624
2625 <div class="si-section si-settings-note">
2626 <div class="si-section-header">
2627 <h3 class="si-section-title"><?php esc_html_e( 'Privacy Note', 'server-info' ); ?></h3>
2628 </div>
2629 <p><?php esc_html_e( 'Server Info only displays diagnostics to administrators. Some values can include internal hostnames, IP addresses, file paths, and debug-log lines, so avoid sharing screenshots publicly without reviewing them first.', 'server-info' ); ?></p>
2630 </div>
2631 </div>
2632 <?php
2633 }
2634
2635 /**
2636 * Displays the support tab.
2637 *
2638 * @return void
2639 */
2640 public static function display_support_tab() {
2641 $review_url = 'https://wordpress.org/support/plugin/server-info/reviews/#new-post';
2642 $support_forum_url = 'https://wordpress.org/support/plugin/server-info/';
2643 ?>
2644 <div class="si-dashboard-grid si-support-grid">
2645 <div class="si-section si-support-hero">
2646 <div class="si-support-hero-icon">
2647 <span class="dashicons dashicons-heart"></span>
2648 </div>
2649 <h3><?php esc_html_e( 'Support Server Info', 'server-info' ); ?></h3>
2650 <p><?php esc_html_e( 'If Server Info helped you debug a hosting issue, prepare a support ticket, or understand a slow site, your support helps keep the plugin maintained and compatible with new WordPress and PHP releases.', 'server-info' ); ?></p>
2651 <div class="si-support-actions">
2652 <button type="button" class="si-btn-primary si-support-checkout-button" data-server-info-support-open>
2653 <span class="dashicons dashicons-heart"></span> <?php esc_html_e( 'Choose Support Amount', 'server-info' ); ?>
2654 </button>
2655 <a class="si-btn-secondary" href="<?php echo esc_url( $review_url ); ?>" target="_blank" rel="noopener noreferrer">
2656 <span class="dashicons dashicons-star-filled"></span> <?php esc_html_e( 'Leave a Review', 'server-info' ); ?>
2657 </a>
2658 <a class="si-btn-secondary" href="<?php echo esc_url( $support_forum_url ); ?>" target="_blank" rel="noopener noreferrer">
2659 <span class="dashicons dashicons-sos"></span> <?php esc_html_e( 'Get Support', 'server-info' ); ?>
2660 </a>
2661 </div>
2662 </div>
2663
2664 <div class="si-section">
2665 <div class="si-section-header">
2666 <h3 class="si-section-title"><?php esc_html_e( 'What Your Support Funds', 'server-info' ); ?></h3>
2667 </div>
2668 <ul class="si-support-list">
2669 <li><span class="dashicons dashicons-yes-alt"></span><?php esc_html_e( 'Compatibility testing for new WordPress and PHP releases.', 'server-info' ); ?></li>
2670 <li><span class="dashicons dashicons-yes-alt"></span><?php esc_html_e( 'Safer diagnostics, redaction, and export workflows.', 'server-info' ); ?></li>
2671 <li><span class="dashicons dashicons-yes-alt"></span><?php esc_html_e( 'Better WooCommerce, cron, cache, and database health checks.', 'server-info' ); ?></li>
2672 <li><span class="dashicons dashicons-yes-alt"></span><?php esc_html_e( 'Faster fixes for hosting-specific edge cases.', 'server-info' ); ?></li>
2673 </ul>
2674 </div>
2675 </div>
2676 <?php
2677 }
2678
2679 /**
2680 * Displays the floating supporter checkout launcher.
2681 *
2682 * @return void
2683 */
2684 public static function display_support_floating_checkout() {
2685 $plans = self::get_support_plans();
2686 ?>
2687 <button type="button" class="si-floating-support-button" data-server-info-support-open>
2688 <span class="dashicons dashicons-heart"></span>
2689 <span><?php esc_html_e( 'Support', 'server-info' ); ?></span>
2690 </button>
2691
2692 <div id="server-info-support-panel" class="si-support-panel" hidden aria-hidden="true">
2693 <div class="si-support-panel-backdrop" data-server-info-support-close></div>
2694 <div class="si-support-panel-card" role="dialog" aria-modal="true" aria-labelledby="server-info-support-panel-title">
2695 <button type="button" class="si-support-panel-close" data-server-info-support-close aria-label="<?php esc_attr_e( 'Close support options', 'server-info' ); ?>">
2696 <span class="dashicons dashicons-no-alt"></span>
2697 </button>
2698 <div class="si-support-panel-header">
2699 <span class="si-support-panel-icon"><span class="dashicons dashicons-heart"></span></span>
2700 <div>
2701 <h3 id="server-info-support-panel-title"><?php esc_html_e( 'Support Server Info', 'server-info' ); ?></h3>
2702 <p><?php esc_html_e( 'Choose any one-time amount that feels right. The plugin stays free for everyone.', 'server-info' ); ?></p>
2703 </div>
2704 </div>
2705 <div class="si-support-plan-grid">
2706 <?php foreach ( $plans as $plan ) : ?>
2707 <button type="button" class="si-support-plan-card <?php echo ! empty( $plan['featured'] ) ? 'is-featured' : ''; ?>" data-server-info-support-plan="<?php echo esc_attr( $plan['id'] ); ?>">
2708 <span class="si-support-plan-title"><?php echo esc_html( $plan['title'] ); ?></span>
2709 <span class="si-support-plan-amount"><?php echo esc_html( $plan['amount'] ); ?></span>
2710 <span class="si-support-plan-desc"><?php echo esc_html( $plan['description'] ); ?></span>
2711 </button>
2712 <?php endforeach; ?>
2713 </div>
2714 <p id="server-info-support-message" class="si-support-message" aria-live="polite"></p>
2715 </div>
2716 </div>
2717 <?php
2718 }
2719
2720 /**
2721 * Display the PHP Info tab.
2722 *
2723 * @return void
2724 */
2725 public static function display_phpinfo_tab() {
2726 $settings = function_exists( 'ini_get_all' ) ? ini_get_all( null, false ) : array();
2727 $settings = is_array( $settings ) ? $settings : array();
2728 ksort( $settings, SORT_NATURAL | SORT_FLAG_CASE );
2729
2730 $extensions = function_exists( 'get_loaded_extensions' ) ? get_loaded_extensions() : array();
2731 sort( $extensions, SORT_NATURAL | SORT_FLAG_CASE );
2732
2733 ?>
2734 <div class="si-section si-phpinfo-wrapper">
2735 <div class="si-section-header">
2736 <h3 class="si-section-title"><?php esc_html_e( 'PHP Runtime Summary', 'server-info' ); ?></h3>
2737 </div>
2738 <ul class="si-table-list">
2739 <li class="si-table-row">
2740 <span class="si-table-label"><?php esc_html_e( 'PHP Version', 'server-info' ); ?></span>
2741 <span class="si-table-val"><?php echo esc_html( PHP_VERSION ); ?></span>
2742 </li>
2743 <li class="si-table-row">
2744 <span class="si-table-label"><?php esc_html_e( 'Server API', 'server-info' ); ?></span>
2745 <span class="si-table-val"><?php echo esc_html( PHP_SAPI ); ?></span>
2746 </li>
2747 <li class="si-table-row has-array">
2748 <span class="si-table-label"><?php esc_html_e( 'Loaded Extensions', 'server-info' ); ?></span>
2749 <span class="si-table-val"><?php echo esc_html( implode( ', ', $extensions ) ); ?></span>
2750 </li>
2751 </ul>
2752 </div>
2753
2754 <div class="si-section si-phpinfo-wrapper">
2755 <div class="si-section-header">
2756 <h3 class="si-section-title"><?php esc_html_e( 'Current PHP Settings', 'server-info' ); ?></h3>
2757 </div>
2758 <?php if ( empty( $settings ) ) : ?>
2759 <p><?php esc_html_e( 'PHP configuration values are unavailable on this server.', 'server-info' ); ?></p>
2760 <?php else : ?>
2761 <ul class="si-table-list">
2762 <?php foreach ( $settings as $name => $value ) : ?>
2763 <?php
2764 $is_sensitive = (bool) preg_match( '/pass(word|wd)?|secret|credential|token/i', (string) $name );
2765 if ( $is_sensitive ) {
2766 $display_value = esc_html__( 'Hidden', 'server-info' );
2767 } elseif ( false === $value || '' === $value ) {
2768 $display_value = esc_html__( 'Not set', 'server-info' );
2769 } else {
2770 $display_value = (string) $value;
2771 }
2772 ?>
2773 <li class="si-table-row">
2774 <span class="si-table-label"><code><?php echo esc_html( $name ); ?></code></span>
2775 <span class="si-table-val"><?php echo esc_html( $display_value ); ?></span>
2776 </li>
2777 <?php endforeach; ?>
2778 </ul>
2779 <?php endif; ?>
2780 </div>
2781 <?php
2782 }
2783
2784 /**
2785 * Displays the diagnostics and logs tab content.
2786 *
2787 * @since 0.0.1
2788 * @access public
2789 *
2790 * @return void
2791 */
2792 public static function display_diagnostics_tab() { $php_version = phpversion();
2793 $php_status = 'Optimal';
2794 $php_impact = 'No Impact';
2795 $php_color = 'var(--si-success)';
2796 if ( version_compare( $php_version, '7.4', '<' ) ) {
2797 $php_status = 'Critical';
2798 $php_impact = '-30% Penalty';
2799 $php_color = 'var(--si-danger)';
2800 } elseif ( version_compare( $php_version, '8.3', '<' ) ) {
2801 $php_status = 'Warning';
2802 $php_impact = '-10% Penalty';
2803 $php_color = 'var(--si-warning)';
2804 }
2805
2806 $memory_limit = ini_get( 'memory_limit' );
2807 $memory_limit_int = intval( $memory_limit );
2808 if ( false !== strpos( $memory_limit, 'G' ) ) {
2809 $memory_limit_int *= 1024;
2810 }
2811 $mem_status = 'Good';
2812 $mem_impact = 'No Impact';
2813 $mem_color = 'var(--si-success)';
2814 if ( $memory_limit_int > 0 && $memory_limit_int < 256 ) {
2815 $mem_status = 'Low';
2816 $mem_impact = '-10% Penalty';
2817 $mem_color = 'var(--si-warning)';
2818 }
2819
2820 $wp_config_path = ABSPATH . 'wp-config.php';
2821 $is_config_writable = file_exists( $wp_config_path ) && wp_is_writable( $wp_config_path );
2822 $config_status = $is_config_writable ? 'Writable (Insecure)' : 'Secure';
2823 $config_impact = $is_config_writable ? '-10% Penalty' : 'No Impact';
2824 $config_color = $is_config_writable ? 'var(--si-danger)' : 'var(--si-success)';
2825
2826 global $wp_version;
2827 $core_updates = get_site_transient('update_core');
2828 $wp_status = 'Up to date';
2829 $wp_impact = 'No Impact';
2830 $wp_color = 'var(--si-success)';
2831 if ( isset( $core_updates->updates ) && is_array( $core_updates->updates ) ) {
2832 foreach ( $core_updates->updates as $update ) {
2833 if ( $update->response === 'upgrade' ) {
2834 $wp_status = 'Update Available';
2835 $wp_impact = '-10% Penalty';
2836 $wp_color = 'var(--si-warning)';
2837 break;
2838 }
2839 }
2840 }
2841
2842 $error_log = ini_get( 'error_log' );
2843 $log_status = ! empty( $error_log ) ? esc_html( $error_log ) : 'Not configured';
2844
2845 ?>
2846 <div class="si-dashboard-grid" style="grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); margin-bottom: 24px;">
2847 <div class="si-card">
2848 <div class="si-plugin-card-header">
2849 <div class="si-plugin-card-icon" style="color: <?php echo esc_attr($php_color); ?>;">
2850 <span class="dashicons dashicons-media-code"></span>
2851 </div>
2852 <h3 class="si-plugin-card-title"><?php esc_html_e( 'PHP Version', 'server-info' ); ?></h3>
2853 </div>
2854 <div style="margin-bottom: 12px;">
2855 <strong><?php echo esc_html($php_version); ?></strong> - <span style="color: <?php echo esc_attr($php_color); ?>; font-weight: 600;"><?php echo esc_html($php_status); ?></span>
2856 </div>
2857 <div style="font-size: 13px; color: var(--si-text-muted); background: var(--si-bg); padding: 8px; border-radius: 6px;">
2858 <strong>Score Impact:</strong> <?php echo esc_html($php_impact); ?>
2859 </div>
2860 </div>
2861
2862 <div class="si-card">
2863 <div class="si-plugin-card-header">
2864 <div class="si-plugin-card-icon" style="color: <?php echo esc_attr($mem_color); ?>;">
2865 <span class="dashicons dashicons-dashboard"></span>
2866 </div>
2867 <h3 class="si-plugin-card-title"><?php esc_html_e( 'PHP Memory Limit', 'server-info' ); ?></h3>
2868 </div>
2869 <div style="margin-bottom: 12px;">
2870 <strong><?php echo esc_html($memory_limit); ?></strong> - <span style="color: <?php echo esc_attr($mem_color); ?>; font-weight: 600;"><?php echo esc_html($mem_status); ?></span>
2871 </div>
2872 <div style="font-size: 13px; color: var(--si-text-muted); background: var(--si-bg); padding: 8px; border-radius: 6px;">
2873 <strong>Score Impact:</strong> <?php echo esc_html($mem_impact); ?>
2874 </div>
2875 </div>
2876
2877 <div class="si-card">
2878 <div class="si-plugin-card-header">
2879 <div class="si-plugin-card-icon" style="color: <?php echo esc_attr($config_color); ?>;">
2880 <span class="dashicons dashicons-lock"></span>
2881 </div>
2882 <h3 class="si-plugin-card-title"><?php esc_html_e( 'wp-config.php', 'server-info' ); ?></h3>
2883 </div>
2884 <div style="margin-bottom: 12px;">
2885 <span style="color: <?php echo esc_attr($config_color); ?>; font-weight: 600;"><?php echo esc_html($config_status); ?></span>
2886 </div>
2887 <div style="font-size: 13px; color: var(--si-text-muted); background: var(--si-bg); padding: 8px; border-radius: 6px;">
2888 <strong>Score Impact:</strong> <?php echo esc_html($config_impact); ?>
2889 </div>
2890 </div>
2891
2892 <div class="si-card">
2893 <div class="si-plugin-card-header">
2894 <div class="si-plugin-card-icon" style="color: <?php echo esc_attr($wp_color); ?>;">
2895 <span class="dashicons dashicons-wordpress"></span>
2896 </div>
2897 <h3 class="si-plugin-card-title"><?php esc_html_e( 'WordPress Core', 'server-info' ); ?></h3>
2898 </div>
2899 <div style="margin-bottom: 12px;">
2900 <strong><?php echo esc_html($wp_version); ?></strong> - <span style="color: <?php echo esc_attr($wp_color); ?>; font-weight: 600;"><?php echo esc_html($wp_status); ?></span>
2901 </div>
2902 <div style="font-size: 13px; color: var(--si-text-muted); background: var(--si-bg); padding: 8px; border-radius: 6px;">
2903 <strong>Score Impact:</strong> <?php echo esc_html($wp_impact); ?>
2904 </div>
2905 </div>
2906
2907 <div class="si-card">
2908 <div class="si-plugin-card-header">
2909 <div class="si-plugin-card-icon" style="color: var(--si-text-main);">
2910 <span class="dashicons dashicons-analytics"></span>
2911 </div>
2912 <h3 class="si-plugin-card-title"><?php esc_html_e( 'Native PHP Error Log', 'server-info' ); ?></h3>
2913 </div>
2914 <div style="margin-bottom: 12px; font-size: 12px; word-break: break-all;">
2915 <?php echo esc_html($log_status); ?>
2916 </div>
2917 <div style="font-size: 13px; color: var(--si-text-muted); background: var(--si-bg); padding: 8px; border-radius: 6px;">
2918 <strong>Score Impact:</strong> No Impact
2919 </div>
2920 </div>
2921 </div>
2922
2923 <div class="si-section">
2924 <div class="si-section-header">
2925 <h3 class="si-section-title"><?php esc_html_e( 'Server Debug Log', 'server-info' ); ?></h3>
2926 </div>
2927 <div class="si-terminal">
2928 <?php
2929 $log_file = WP_CONTENT_DIR . '/debug.log';
2930 require_once ABSPATH . 'wp-admin/includes/file.php';
2931 WP_Filesystem();
2932 global $wp_filesystem;
2933 if ( $wp_filesystem && $wp_filesystem->exists( $log_file ) && $wp_filesystem->is_readable( $log_file ) ) {
2934 $contents = $wp_filesystem->get_contents( $log_file );
2935 $lines = is_string( $contents ) ? preg_split( '/\R/', $contents ) : array();
2936 if ( is_array( $lines ) && ! empty( $lines ) ) {
2937 $last_lines = array_slice( $lines, -30 );
2938 foreach ( $last_lines as $line ) {
2939 echo esc_html( $line ) . '<br/>';
2940 }
2941 } else {
2942 echo esc_html__( 'Debug log is currently empty.', 'server-info' );
2943 }
2944 } else {
2945 echo esc_html__( 'Debug log not found or not readable. Ensure WP_DEBUG and WP_DEBUG_LOG are enabled in wp-config.php.', 'server-info' );
2946 }
2947 ?>
2948 </div>
2949 </div>
2950 <?php
2951 }
2952 }
2953
2954 // Instantiate the Server_Info class
2955 $Server_Info = Server_Info::getInstance();
2956