PluginProbe
phpinfo() WP – Site Health, PHP Compatibility & Server Audit / 7.2.6
phpinfo() WP – Site Health, PHP Compatibility & Server Audit v7.2.6
7.2.7 7.2.6 7.2.5 7.2.4 7.2.3 7.2.0 7.2.1 7.2.2 7.1.0 7.0.3 7.0.4 7.0.5 trunk 6.0 7.0.0 7.0.1 7.0.2
phpinfo-wp / phpinfo-wp.php

phpinfo-wp.php in phpinfo() WP – Site Health, PHP Compatibility & Server Audit 7.2.6, at phpinfo-wp.php

899 lines 46.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: phpinfo() WP
4 Plugin URI: https://exeebit.com/phpinfo-wp
5 Description: WordPress server health audit — PHP EOL timeline, config grader, security headers, SSL monitor, OPcache, error log, audit reports for clients. Free phpinfo viewer & .htaccess editor included.
6 Version: 7.2.6
7 Requires PHP: 7.3
8 Author: Exeebit
9 Author URI: https://exeebit.com/phpinfo-wp
10 License: GPLv3
11 License URI: https://www.gnu.org/licenses/gpl-3.0.html
12 Text Domain: phpinfo-wp
13 Domain Path: /languages
14 */
15
16 defined('ABSPATH') or die('Unauthorized Access');
17
18 define('PHPINFOWP_VERSION', '7.2.6');
19 define('PHPINFOWP_DIR', plugin_dir_path(__FILE__));
20 define('PHPINFOWP_URL', plugin_dir_url(__FILE__));
21
22 require_once PHPINFOWP_DIR . 'includes/class-license.php';
23 require_once PHPINFOWP_DIR . 'includes/class-eol.php';
24 require_once PHPINFOWP_DIR . 'includes/class-security-headers.php';
25 require_once PHPINFOWP_DIR . 'includes/class-snapshots.php';
26 require_once PHPINFOWP_DIR . 'includes/class-error-log.php';
27 require_once PHPINFOWP_DIR . 'includes/class-opcache.php';
28 require_once PHPINFOWP_DIR . 'includes/class-object-cache.php';
29 require_once PHPINFOWP_DIR . 'includes/class-config-grader.php';
30 require_once PHPINFOWP_DIR . 'includes/class-alerts.php';
31 require_once PHPINFOWP_DIR . 'includes/class-ssl.php';
32 require_once PHPINFOWP_DIR . 'includes/class-site-health.php';
33 require_once PHPINFOWP_DIR . 'includes/class-compat.php';
34 require_once PHPINFOWP_DIR . 'includes/class-update-audit.php';
35 require_once PHPINFOWP_DIR . 'includes/class-db-health.php';
36 require_once PHPINFOWP_DIR . 'includes/class-cron-monitor.php';
37 require_once PHPINFOWP_DIR . 'includes/class-mail-check.php';
38 require_once PHPINFOWP_DIR . 'includes/class-report.php';
39 require_once PHPINFOWP_DIR . 'includes/class-network.php';
40 require_once PHPINFOWP_DIR . 'includes/class-cli.php';
41 require_once PHPINFOWP_DIR . 'includes/class-admin-bar.php';
42 require_once PHPINFOWP_DIR . 'includes/class-admin-nav.php';
43 require_once PHPINFOWP_DIR . 'includes/class-safemode.php';
44 require_once PHPINFOWP_DIR . 'includes/class-config-grader-fixer.php';
45 require_once PHPINFOWP_DIR . 'includes/class-abilities.php';
46 require_once PHPINFOWP_DIR . 'includes/class-ai-explain.php';
47 require_once PHPINFOWP_DIR . 'includes/class-upgrade-notice.php';
48 require_once PHPINFOWP_DIR . 'includes/class-deactivate-modal.php';
49 require_once PHPINFOWP_DIR . 'includes/class-api-monitor.php';
50 require_once PHPINFOWP_DIR . 'includes/class-permissions.php';
51 require_once PHPINFOWP_DIR . 'includes/class-whats-new-notice.php';
52 require_once PHPINFOWP_DIR . 'includes/fn-feature-lock.php';
53
54 if (!class_exists('Phpinfo_wp')):
55
56 class Phpinfo_wp {
57
58 public function register(): void {
59 add_action('admin_menu', [$this, 'add_admin_pages']);
60 add_action('admin_bar_menu', [$this, 'admin_bar_indicator'], 100);
61 add_action('admin_enqueue_scripts', [$this, 'enqueue']);
62 add_action('admin_notices', [$this, 'eol_admin_notice']);
63 add_action('wp_dashboard_setup', [$this, 'register_dashboard_widget']);
64 add_action('phpinfowp_license_ping', ['Phpinfo_WP_License', 'cron_ping']);
65 add_action('phpinfowp_license_ping_async',['Phpinfo_WP_License', 'cron_ping']);
66 add_action('phpinfowp_weekly_maintenance',['Phpinfo_WP_Alerts', 'cron_weekly']);
67 add_filter('clean_url', [$this, 'script_async'], 11, 1);
68 add_filter('plugin_row_meta', [$this, 'meta'], 10, 2);
69 add_filter('plugin_action_links', [$this, 'action_links'], 10, 2);
70 add_filter('admin_body_class', [$this, 'admin_body_class']);
71 // When viewing a leaf page (e.g. Config Grader), highlight its group
72 // (Audit) in the WP submenu — the actual leaf <li> is CSS-hidden, so
73 // without this nothing in the submenu appears active.
74 add_filter('submenu_file', [$this, 'fix_submenu_file']);
75 // Inject Elementor-style flyout panels into the WP admin sidebar so
76 // hovering Audit/Tools/Reports reveals all their leaf pages. Loaded
77 // on every admin page because users hover from anywhere in admin.
78 add_action('admin_footer', [$this, 'render_sidebar_flyouts']);
79 // Leaf-hiding CSS must load early (not in admin_footer) because WP 7.0
80 // View Transitions can skip footer scripts on cross-doc navigations.
81 add_action('admin_enqueue_scripts', [$this, 'enqueue_sidebar_css']);
82 // Topbar (logo + page title + CTA) above every plugin page.
83 add_action('in_admin_header', [$this, 'render_topbar']);
84 Phpinfo_WP_Site_Health::register();
85 Phpinfo_WP_Network::register();
86 Phpinfo_WP_Safemode::register();
87 Phpinfo_WP_Compat::register_update_warnings();
88 Phpinfo_WP_Update_Audit::register();
89 Phpinfo_WP_Abilities::register();
90 Phpinfo_WP_AI_Explain::register();
91 Phpinfo_WP_Upgrade_Notice::register();
92 Phpinfo_WP_Deactivate_Modal::register();
93 Phpinfo_WP_API_Monitor::register();
94 Phpinfo_WP_Permissions::register();
95 Phpinfo_WP_Whats_New_Notice::register();
96
97 // Auto-render the tab bar above every grouped page (priority 5 so
98 // it sits at the top of the .wrap before any other admin notices).
99 add_action('admin_notices', ['Phpinfo_WP_Admin_Nav', 'auto_render'], 5);
100
101 if (is_admin()) {
102 register_shutdown_function(['Phpinfo_WP_Admin_Bar', 'record_memory_peak']);
103 }
104 add_action('init', [$this, 'load_textdomain']);
105 add_action('admin_init', [$this, 'admin_init_migrations']);
106 add_action('admin_init', [$this, 'redirect_group_landings']);
107 add_filter('admin_footer_text', [$this, 'custom_admin_footer_text']);
108 add_filter('update_footer', [$this, 'custom_update_footer'], 20);
109 add_action('in_plugin_update_message-' . plugin_basename(__FILE__), [$this, 'show_update_notice'], 10, 2);
110 }
111
112 public function redirect_group_landings(): void {
113 if (!is_admin()) return;
114 $page = sanitize_key($_GET['page'] ?? '');
115 $redirects = [
116 'phpinfowp-performance' => 'phpinfowp-config-grader',
117 'phpinfowp-audit' => 'phpinfowp-eol',
118 'phpinfowp-tools' => 'phpinfowp-viewer',
119 'phpinfowp-reports' => 'phpinfowp-log',
120 ];
121 if (isset($redirects[$page])) {
122 wp_safe_redirect(admin_url('admin.php?page=' . $redirects[$page]));
123 exit;
124 }
125 }
126
127 public function load_textdomain(): void {
128 load_plugin_textdomain('phpinfo-wp', false, dirname(plugin_basename(__FILE__)) . '/languages');
129 }
130
131 public function admin_init_migrations(): void {
132 $b = get_option('phpinfowp_report_branding');
133 if (is_array($b) && isset($b['tagline']) && $b['tagline'] === 'Yearly Report') {
134 $b['tagline'] = 'Server Health Audit';
135 update_option('phpinfowp_report_branding', $b);
136 }
137 }
138
139 public function show_update_notice(array $plugin_data, object $response): void {
140 echo '<div style="margin-top: 10px; padding: 12px 14px; background: #f6f9fc; border-left: 4px solid #777BB3; font-size: 13px; color: #2c3338; border-radius: 0 4px 4px 0; box-shadow: 0 1px 2px rgba(0,0,0,0.03);">';
141 echo '<p style="margin: 0 0 6px; font-weight: 600; color: #1d2327;">';
142 echo '' . esc_html__('What you are unlocking in this version:', 'phpinfo-wp');
143 echo '</p>';
144 echo '<p style="margin: 0 0 8px; color: #475569; line-height: 1.4;">';
145 echo esc_html__('Includes new translation packs (German, French, Spanish, Italian, Dutch) and optimized PHP server lifecycle audits.', 'phpinfo-wp');
146 echo '</p>';
147 echo '<p style="margin: 0; font-size: 12px; color: #64748b; font-style: italic;">';
148 echo esc_html__('💡 Note: Since this is a system diagnostics tool, standard backup practices are always recommended before upgrading.', 'phpinfo-wp');
149 echo '</p>';
150 echo '</div>';
151 }
152
153 public function add_admin_pages(): void {
154 // Top-level: opens the Dashboard storefront. The old top-level slug
155 // 'phpinfo-wp' kept its identity for SEO + backward compatibility,
156 // but now lands on Dashboard instead of phpinfo() output.
157 add_menu_page(
158 'phpinfo() WP', 'phpinfo() WP', 'manage_options',
159 'phpinfo-wp', [$this, 'view_dashboard'],
160 self::menu_icon(), 99
161 );
162
163 // The 5 VISIBLE sidebar items. Everything else is registered below
164 // as a hidden submenu so direct ?page=X links still resolve.
165 add_submenu_page('phpinfo-wp', 'Dashboard', 'Dashboard',
166 'manage_options', 'phpinfo-wp', [$this, 'view_dashboard']);
167
168 add_submenu_page('phpinfo-wp', 'Performance', 'Performance',
169 'manage_options', 'phpinfowp-performance', [$this, 'view_performance_group']);
170
171 add_submenu_page('phpinfo-wp', 'Security & Core', 'Security & Core',
172 'manage_options', 'phpinfowp-audit', [$this, 'view_audit_group']);
173
174 add_submenu_page('phpinfo-wp', 'Tools', 'Tools',
175 'manage_options', 'phpinfowp-tools', [$this, 'view_tools_group']);
176
177 add_submenu_page('phpinfo-wp', 'Reports', 'Reports',
178 'manage_options', 'phpinfowp-reports', [$this, 'view_reports_group']);
179
180 add_submenu_page('phpinfo-wp', 'License', 'License',
181 'manage_options', 'phpinfowp-license', [$this, 'view_license']);
182
183 add_submenu_page('phpinfo-wp', 'Support', 'Support',
184 'manage_options', 'phpinfowp-support', [$this, 'view_support']);
185
186 // Hidden registrations — these slugs work as direct URLs (admin bar,
187 // dashboard widget, admin notices) but never appear in the sidebar.
188 // Passing null as the parent is the WP-canonical way to register a
189 // hidden admin page (supported since WP 5.3): the page is registered
190 // in $_registered_pages and the action hook is bound, but it isn't
191 // appended to $submenu so it never clutters the sidebar.
192 $hidden = [
193 // Audit group
194 ['phpinfowp-config-grader', 'Config Grader', 'view_config_grader'],
195 ['phpinfowp-eol', 'PHP EOL', 'view_eol'],
196 ['phpinfowp-compat', 'PHP Compatibility', 'view_compat'],
197 ['phpinfowp-update-audit', 'Update Guard', 'view_update_audit'],
198 ['phpinfowp-security-headers', 'Security Headers', 'view_security_headers'],
199 ['phpinfowp-ssl', 'SSL Monitor', 'view_ssl'],
200 ['phpinfowp-opcache', 'OPcache', 'view_opcache'],
201 ['phpinfowp-object-cache', 'Object Cache', 'view_object_cache'],
202 ['phpinfowp-db-health', 'Database Health', 'view_db_health'],
203 ['phpinfowp-api-monitor', 'API Monitor', 'view_api_monitor'],
204 ['phpinfowp-permissions', 'Permissions Audit', 'view_permissions'],
205 // Tools group
206 ['phpinfowp-htaccess', 'PHP Config', 'view_htaccess'],
207 ['phpinfowp-safemode', 'Troubleshooting', 'view_safemode'],
208 ['phpinfowp-viewer', 'phpinfo() Viewer', 'view_phpinfo'],
209 ['phpinfowp-extensions', 'Extensions', 'view_extensions'],
210 ['phpinfowp-info', 'Basic Info', 'view_info'],
211 ['phpinfowp-snapshots', 'Config Snapshots', 'view_snapshots'],
212 ['phpinfowp-cron', 'WP Cron Monitor', 'view_cron'],
213 ['phpinfowp-mail', 'Mail', 'view_mail'],
214 ['phpinfowp-error-log', 'Error Log', 'view_error_log'],
215 // Reports group
216 ['phpinfowp-log', 'Activity Log', 'view_log'],
217 ['phpinfowp-report', 'Audit Report', 'view_report'],
218 ['phpinfowp-alerts', 'Alerts', 'view_alerts'],
219 ];
220 // Register every leaf page as a real child of phpinfo-wp so WP
221 // natively highlights the toplevel + its submenu on every leaf
222 // (parent_file/submenu_file filters were unreliable here). We hide
223 // the extra <li>s from the visible sidebar via JS in render_sidebar_flyouts
224 // — only the 5 grouped items above stay visible.
225 foreach ($hidden as [$slug, $title, $cb]) {
226 add_submenu_page('phpinfo-wp', $title, $title, 'manage_options', $slug, [$this, $cb]);
227 }
228 }
229
230 // --- Top-level + group routers ---
231 public function view_dashboard(): void { require PHPINFOWP_DIR . 'views/dashboard.php'; }
232 public function view_performance_group(): void { Phpinfo_WP_Admin_Nav::render_group('performance'); }
233 public function view_audit_group(): void { Phpinfo_WP_Admin_Nav::render_group('audit'); }
234 public function view_tools_group(): void { Phpinfo_WP_Admin_Nav::render_group('tools'); }
235 public function view_reports_group(): void { Phpinfo_WP_Admin_Nav::render_group('reports'); }
236
237 // --- Free views ---
238 public function view_phpinfo(): void { self::thankyou(); require PHPINFOWP_DIR . 'views/phpinfo.php'; }
239 public function view_htaccess(): void { require PHPINFOWP_DIR . 'views/htaccess.php'; }
240 public function view_extensions(): void { require PHPINFOWP_DIR . 'views/extension.php'; }
241 public function view_info(): void { require PHPINFOWP_DIR . 'views/info.php'; }
242 public function view_log(): void { require PHPINFOWP_DIR . 'views/log.php'; }
243 public function view_safemode(): void { require PHPINFOWP_DIR . 'views/safemode.php'; }
244
245 // --- Pro views ---
246 public function view_eol(): void { require PHPINFOWP_DIR . 'views/pro/eol.php'; }
247 public function view_security_headers(): void{ require PHPINFOWP_DIR . 'views/pro/security-headers.php'; }
248 public function view_snapshots(): void { require PHPINFOWP_DIR . 'views/pro/snapshots.php'; }
249 public function view_error_log(): void { require PHPINFOWP_DIR . 'views/pro/error-log.php'; }
250 public function view_opcache(): void { require PHPINFOWP_DIR . 'views/pro/opcache.php'; }
251 public function view_object_cache(): void { require PHPINFOWP_DIR . 'views/pro/object-cache.php'; }
252 public function view_config_grader(): void { require PHPINFOWP_DIR . 'views/pro/config-grader.php'; }
253 public function view_alerts(): void { require PHPINFOWP_DIR . 'views/pro/alerts.php'; }
254 public function view_ssl(): void { require PHPINFOWP_DIR . 'views/pro/ssl.php'; }
255 public function view_compat(): void { require PHPINFOWP_DIR . 'views/pro/compat.php'; }
256 public function view_update_audit(): void { require PHPINFOWP_DIR . 'views/pro/update-audit.php'; }
257 public function view_db_health(): void { require PHPINFOWP_DIR . 'views/pro/db-health.php'; }
258 public function view_cron(): void { require PHPINFOWP_DIR . 'views/pro/cron.php'; }
259 public function view_mail(): void { require PHPINFOWP_DIR . 'views/pro/mail.php'; }
260 public function view_report(): void { require PHPINFOWP_DIR . 'views/pro/report.php'; }
261 public function view_license(): void { require PHPINFOWP_DIR . 'views/pro/license.php'; }
262 public function view_api_monitor(): void { require PHPINFOWP_DIR . 'views/pro/api-monitor.php'; }
263 public function view_permissions(): void { require PHPINFOWP_DIR . 'views/pro/permissions.php'; }
264 public function view_support(): void { require PHPINFOWP_DIR . 'views/pro/support.php'; }
265
266 public function admin_bar_indicator(WP_Admin_Bar $bar): void {
267 Phpinfo_WP_Admin_Bar::render($bar);
268 }
269
270 public function register_dashboard_widget(): void {
271 wp_add_dashboard_widget(
272 'phpinfowp_health',
273 'PHP Health — phpinfo() WP',
274 [$this, 'render_dashboard_widget']
275 );
276 }
277
278 public function render_dashboard_widget(): void {
279 $eol = Phpinfo_WP_EOL::status();
280 $is_pro = Phpinfo_WP_License::is_valid();
281
282 switch ($eol['status']) {
283 case 'eol':
284 $eol_color = '#d63638';
285 break;
286 case 'warning':
287 $eol_color = '#dba617';
288 break;
289 default:
290 $eol_color = '#00a32a';
291 break;
292 }
293 switch ($eol['status']) {
294 case 'eol':
295 $eol_label = 'END OF LIFE';
296 break;
297 case 'warning':
298 $eol_label = 'EXPIRING SOON';
299 break;
300 default:
301 $eol_label = 'SUPPORTED';
302 break;
303 }
304
305 $mem_used_raw = memory_get_usage(true);
306 $mem_limit_raw = self::_parse_bytes(ini_get('memory_limit'));
307 $mem_used_fmt = size_format($mem_used_raw);
308 $mem_limit_fmt = ini_get('memory_limit');
309 $mem_pct = $mem_limit_raw > 0 ? min(100, round($mem_used_raw / $mem_limit_raw * 100)) : 0;
310 $mem_bar_color = $mem_pct > 85 ? '#d63638' : ($mem_pct > 65 ? '#dba617' : '#777BB3');
311 ?>
312 <div style="font-size:13px;line-height:1.6">
313
314 <!-- PHP version + EOL -->
315 <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #f0f0f0">
316 <div>
317 <span style="font-size:20px;font-weight:700;color:#1d2327">PHP <?php echo esc_html(PHP_VERSION); ?></span>
318 <div style="font-size:11px;color:#666;margin-top:1px">EOL: <?php echo esc_html($eol['eol']); ?>
319 <?php if ($eol['status'] !== 'eol'): ?>
320 · <?php echo esc_html($eol['days']); ?> days
321 <?php endif; ?>
322 </div>
323 </div>
324 <span style="font-size:10px;font-weight:700;letter-spacing:.5px;padding:3px 8px;border-radius:3px;background:<?php echo $eol_color; ?>;color:#fff">
325 <?php echo esc_html($eol_label); ?>
326 </span>
327 </div>
328
329 <!-- Memory -->
330 <div style="padding:10px 0;border-bottom:1px solid #f0f0f0">
331 <div style="display:flex;justify-content:space-between;margin-bottom:5px">
332 <span style="color:#555">Memory</span>
333 <span style="color:#333;font-weight:500"><?php echo esc_html($mem_used_fmt); ?> / <?php echo esc_html($mem_limit_fmt); ?></span>
334 </div>
335 <div style="background:#e0e0e0;border-radius:3px;height:6px;overflow:hidden">
336 <div style="width:<?php echo $mem_pct; ?>%;height:6px;background:<?php echo $mem_bar_color; ?>;border-radius:3px;transition:width .3s"></div>
337 </div>
338 </div>
339
340 <?php
341 // Config grade — shown for everyone; free shows summary, Pro adds OPcache + SSL detail
342 $grade_summary = $is_pro
343 ? Phpinfo_WP_Config_Grader::run()
344 : Phpinfo_WP_Config_Grader::summary();
345 $grade_score = $grade_summary['score'] ?? 0;
346 $grade_letter = $grade_summary['grade'] ?? 'F';
347 switch (true) {
348 case $grade_score >= 85:
349 $grade_color = '#00a32a';
350 break;
351 case $grade_score >= 60:
352 $grade_color = '#dba617';
353 break;
354 default:
355 $grade_color = '#d63638';
356 break;
357 }
358 ?>
359 <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #f0f0f0">
360 <span style="color:#555">Config Grade</span>
361 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-config-grader')); ?>" style="text-decoration:none;font-weight:700;font-size:16px;color:<?php echo $grade_color; ?>">
362 <?php echo esc_html($grade_letter); ?> <span style="font-size:11px;font-weight:400;color:#666"><?php echo (int)$grade_score; ?>/100</span>
363 </a>
364 </div>
365
366 <?php if ($is_pro): ?>
367
368 <!-- OPcache -->
369 <?php if (Phpinfo_WP_OPcache::is_available()):
370 $oc = Phpinfo_WP_OPcache::status();
371 if ($oc && $oc['enabled'] && $oc['hit_rate'] !== null): ?>
372 <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #f0f0f0">
373 <span style="color:#555">OPcache</span>
374 <span style="font-weight:600;color:<?php echo $oc['hit_rate'] >= 90 ? '#00a32a' : ($oc['hit_rate'] >= 70 ? '#dba617' : '#d63638'); ?>">
375 <?php echo esc_html($oc['hit_rate']); ?>% hit rate
376 </span>
377 </div>
378 <?php endif; endif; ?>
379
380 <!-- SSL -->
381 <?php
382 $ssl_results = Phpinfo_WP_SSL::check_all();
383 $ssl_worst = null;
384 foreach ($ssl_results as $r) {
385 if (!empty($r['error'])) continue;
386 if ($ssl_worst === null || $r['days'] < $ssl_worst['days']) $ssl_worst = $r;
387 }
388 if ($ssl_worst): ?>
389 <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #f0f0f0">
390 <span style="color:#555">SSL <span style="font-size:11px;color:#999">(<?php echo esc_html($ssl_worst['host']); ?>)</span></span>
391 <span style="font-weight:600;color:<?php echo esc_html(Phpinfo_WP_SSL::status_color($ssl_worst['status'])); ?>">
392 <?php echo $ssl_worst['days'] < 0
393 ? 'EXPIRED'
394 : esc_html($ssl_worst['days']) . ' days'; ?>
395 </span>
396 </div>
397 <?php endif; ?>
398
399 <?php else:
400 // Data-driven upsell — speak to what's actually broken on THIS site
401 $upsell_lines = [];
402 if ($eol['status'] === 'eol') {
403 $upsell_lines[] = sprintf('PHP %s reached end-of-life on %s. Pro scans every plugin for compatibility before you upgrade.', esc_html($eol['minor']), esc_html($eol['eol']));
404 } elseif ($eol['status'] === 'warning') {
405 $upsell_lines[] = sprintf('PHP %s reaches EOL in %d days. Pro scans every plugin for compatibility before you upgrade.', esc_html($eol['minor']), (int)$eol['days']);
406 }
407 $fails = (int)($grade_summary['fails'] ?? 0);
408 if ($fails > 0) {
409 $upsell_lines[] = sprintf('%d failing config check%s. Pro shows exactly what to change and where.', $fails, $fails === 1 ? '' : 's');
410 }
411 if (!$upsell_lines) {
412 $upsell_lines[] = 'Track SSL expiry, scan security headers, monitor OPcache, and email yourself a weekly digest.';
413 }
414 ?>
415 <div style="padding:12px 0 8px 0;border-bottom:1px solid #f0f0f0">
416 <?php foreach ($upsell_lines as $line): ?>
417 <div style="font-size:12px;color:#555;margin-bottom:6px;line-height:1.5"> <?php echo $line; ?></div>
418 <?php endforeach; ?>
419 <a href="https://exeebit.com/phpinfo-wp#pricing" target="_blank" style="display:inline-block;margin-top:6px;color:#777BB3;font-weight:600;text-decoration:none">
420 Upgrade to Pro &rarr;
421 </a>
422 </div>
423
424 <?php endif; ?>
425
426 <!-- Footer link -->
427 <div style="padding-top:10px;display:flex;gap:12px;font-size:12px;flex-wrap:wrap">
428 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-viewer')); ?>">Full phpinfo </a>
429 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-eol')); ?>">PHP EOL</a>
430 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-config-grader')); ?>">Config Grader</a>
431 <?php if ($is_pro): ?>
432 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-report')); ?>">Audit Report</a>
433 <a href="<?php echo esc_url(admin_url('admin.php?page=phpinfowp-alerts')); ?>">Alerts</a>
434 <?php endif; ?>
435 </div>
436
437 </div>
438 <?php
439 }
440
441 // Custom SVG menu icon: phpinfo() — bold parens with center dot.
442 // WP applies admin color scheme via CSS mask, so a single-color SVG is correct here.
443 public static function menu_icon(): string {
444 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="black">'
445 . '<path d="M7.4 2.6 C 3.4 6.2 3.4 13.8 7.4 17.4 L 9 16 C 5.8 12.8 5.8 7.2 9 4 Z"/>'
446 . '<path d="M12.6 2.6 C 16.6 6.2 16.6 13.8 12.6 17.4 L 11 16 C 14.2 12.8 14.2 7.2 11 4 Z"/>'
447 . '<circle cx="10" cy="10" r="1.6"/>'
448 . '</svg>';
449 return 'data:image/svg+xml;base64,' . base64_encode($svg);
450 }
451
452 private static function _parse_bytes(string $val): int {
453 $val = trim($val);
454 $last = strtolower(substr($val, -1));
455 $num = (int) $val;
456 switch ($last) {
457 case 'g':
458 return $num * 1073741824;
459 case 'm':
460 return $num * 1048576;
461 case 'k':
462 return $num * 1024;
463 default:
464 return $num;
465 }
466 }
467
468 // EOL notice — free feature, only on plugin admin pages
469 public function eol_admin_notice(): void {
470 $page = sanitize_key($_GET['page'] ?? '');
471 if (strncmp($page, 'phpinfo', strlen('phpinfo')) !== 0) return;
472 Phpinfo_WP_EOL::admin_notice();
473 }
474
475 public function enqueue(string $hook): void {
476 $page = sanitize_key($_GET['page'] ?? '');
477 if (strncmp($page, 'phpinfo', strlen('phpinfo')) !== 0 && $hook !== 'plugins.php') return;
478 // Use filemtime as the cache key so any CSS/JS edit auto-busts browser
479 // caches without bumping PHPINFOWP_VERSION. Falls back to plugin version.
480 $css_path = PHPINFOWP_DIR . 'css/style.css';
481 $js_path = PHPINFOWP_DIR . 'js/scripts.js';
482 $css_ver = file_exists($css_path) ? (string) filemtime($css_path) : PHPINFOWP_VERSION;
483 $js_ver = file_exists($js_path) ? (string) filemtime($js_path) : PHPINFOWP_VERSION;
484 wp_enqueue_style('phpinfowp', PHPINFOWP_URL . 'css/style.css', ['dashicons'], $css_ver);
485 wp_enqueue_script('phpinfowp', PHPINFOWP_URL . 'js/scripts.js#async', [], $js_ver, true);
486
487 if (Phpinfo_WP_AI_Explain::available()) {
488 $ai_path = PHPINFOWP_DIR . 'js/ai-explain.js';
489 $ai_ver = file_exists($ai_path) ? (string) filemtime($ai_path) : PHPINFOWP_VERSION;
490 wp_enqueue_script('phpinfowp-ai-explain', PHPINFOWP_URL . 'js/ai-explain.js', [], $ai_ver, true);
491 wp_localize_script('phpinfowp-ai-explain', 'phpinfowpAI', Phpinfo_WP_AI_Explain::js_config());
492 }
493 }
494
495 /**
496 * Inject sidebar leaf-hiding + flyout CSS on every admin page via
497 * wp_add_inline_style. This fires during admin_enqueue_scripts
498 * (not admin_footer) so the CSS is present before the first paint
499 * and survives WP 7.0 View Transition navigations.
500 */
501 public function enqueue_sidebar_css(): void {
502 $groups = Phpinfo_WP_Admin_Nav::groups();
503
504 // Build leaf-hiding selectors for every tab slug.
505 $leaf_css = [];
506 foreach ($groups as $group) {
507 foreach ($group['tabs'] as $slug => $tab) {
508 $leaf_css[] = '#toplevel_page_phpinfo-wp .wp-submenu li:has(> a[href$="page=' . $slug . '"])';
509 $leaf_css[] = '#toplevel_page_phpinfo-wp .wp-submenu li:has(> a[href*="page=' . $slug . '&"])';
510 }
511 }
512
513 $css = '';
514 if ($leaf_css) {
515 $css .= "@supports selector(:has(*)) {\n";
516 $css .= implode(",\n", $leaf_css);
517 $css .= " { display: none !important; }\n}\n";
518 }
519
520 // Flyout panel base styles (structure + WP 7.0 native colors).
521 $css .= <<<'FLYOUT'
522 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout { position: relative; }
523 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout > a { padding-right: 22px; }
524 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout > a::after {
525 content: "\203A"; position: absolute; right: 10px; top: 50%;
526 transform: translateY(-50%); opacity: .55; font-size: 16px; line-height: 1;
527 }
528 .phpinfowp-wpnav-flyout {
529 display: none; position: fixed; min-width: 210px;
530 max-height: calc(100vh - 40px); overflow-y: auto;
531 background-color: rgb(12.15,12.15,12.15); padding: 6px 0;
532 z-index: 9999; box-shadow: 0 3px 5px rgba(0,0,0,.2);
533 }
534 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout:hover > .phpinfowp-wpnav-flyout,
535 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout:focus-within > .phpinfowp-wpnav-flyout {
536 display: block;
537 }
538 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a {
539 display: flex !important; align-items: center; justify-content: space-between;
540 gap: 8px; margin: 0; padding: 5px 12px !important;
541 color: rgba(240,246,252,0.7) !important; text-decoration: none !important;
542 font-size: 13px !important; line-height: 1.4 !important; font-weight: 400 !important;
543 }
544 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a:hover,
545 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a:focus {
546 color: #7b90ff !important; background: none !important;
547 box-shadow: inset 4px 0 0 0 currentColor; transition: box-shadow .1s linear;
548 }
549 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a.phpinfowp-flyout-active,
550 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a.phpinfowp-flyout-active:hover,
551 #toplevel_page_phpinfo-wp .wp-submenu li.phpinfowp-has-flyout .phpinfowp-wpnav-flyout a.phpinfowp-flyout-active:focus {
552 color: #7b90ff !important; background: none !important;
553 box-shadow: inset 4px 0 0 0 currentColor;
554 }
555 .phpinfowp-wpnav-flyout-pro {
556 display: inline-block; background: #777BB3; color: #fff;
557 font-size: 9px; font-weight: 700; letter-spacing: .4px;
558 padding: 1px 5px; border-radius: 3px;
559 }
560 body.folded #toplevel_page_phpinfo-wp .phpinfowp-wpnav-flyout { display: none !important; }
561
562 #toplevel_page_phpinfo-wp .wp-submenu > li > a:hover,
563 #toplevel_page_phpinfo-wp .wp-submenu > li > a:focus,
564 #toplevel_page_phpinfo-wp .wp-submenu > li.current > a,
565 #toplevel_page_phpinfo-wp .wp-submenu > li.current > a:hover,
566 #toplevel_page_phpinfo-wp .wp-submenu > li.current > a:focus {
567 color: #7b90ff !important;
568 }
569 FLYOUT;
570
571 // Attach to WP's built-in common stylesheet — always loaded.
572 wp_add_inline_style('common', $css);
573 }
574
575 public function script_async(string $url): string {
576 if (strpos($url, '#async') === false) return $url;
577 return str_replace('#async', '', $url) . "' async='async";
578 }
579
580 public function meta(array $links, string $file): array {
581 if (strpos($file, 'phpinfo-wp/phpinfo-wp.php') === false) return $links;
582 if (Phpinfo_WP_License::is_valid()) {
583 $links[] = '<span style="color:#00a32a;font-weight:600">&#10003; Pro Active</span>';
584 } else {
585 $links[] = '<a href="https://exeebit.com/phpinfo-wp#pricing" target="_blank" style="color:#777BB3;font-weight:600">Upgrade to Pro</a>';
586 }
587 return $links;
588 }
589
590 public function action_links(array $links, string $plugin_file): array {
591 if (plugin_basename(__FILE__) !== $plugin_file) return $links;
592 // Order: Dashboard | …WP defaults (Deactivate)… | Get Pro
593 array_unshift($links, '<a href="' . admin_url('admin.php?page=phpinfo-wp') . '">Dashboard</a>');
594 if (!Phpinfo_WP_License::is_valid()) {
595 $links[] = '<a href="https://exeebit.com/phpinfo-wp#pricing" target="_blank" style="color:#777BB3;font-weight:600">Get Pro</a>';
596 }
597 return $links;
598 }
599
600 // Inject hover-flyout panels into the WP admin sidebar for the three
601 // grouped items (Audit / Tools / Reports). The toplevel <li> is found
602 // by id (#toplevel_page_phpinfo-wp); each group's submenu <li> is found
603 // by its admin.php?page= slug. CSS handles the actual hover reveal.
604 public function render_sidebar_flyouts(): void {
605 $groups = Phpinfo_WP_Admin_Nav::groups();
606 $is_pro = Phpinfo_WP_License::is_valid();
607
608 $data = [];
609 foreach ($groups as $key => $group) {
610 $items = [];
611 foreach ($group['tabs'] as $slug => $tab) {
612 $items[] = [
613 'url' => admin_url('admin.php?page=' . $slug),
614 'label' => $tab['label'],
615 'pro' => !empty($tab['pro']) && !$is_pro,
616 ];
617 }
618 $data[$group['slug']] = ['label' => $group['label'], 'items' => $items];
619 }
620 ?>
621 <style id="phpinfowp-wpnav-flyout-css">
622 /* Duplicate of enqueue_sidebar_css — kept as fallback for edge cases
623 where admin_enqueue_scripts didn't fire (e.g. mu-plugin conflicts).
624 The browser deduplicates identical declarations, so no harm. */
625 <?php
626 $leaf_css = [];
627 foreach ($groups as $group) {
628 foreach ($group['tabs'] as $lslug => $ltab) {
629 $leaf_css[] = '#toplevel_page_phpinfo-wp .wp-submenu li:has(> a[href$="page=' . $lslug . '"])';
630 $leaf_css[] = '#toplevel_page_phpinfo-wp .wp-submenu li:has(> a[href*="page=' . $lslug . '&"])';
631 }
632 }
633 if ($leaf_css) {
634 echo "@supports selector(:has(*)) {\n";
635 echo implode(",\n", $leaf_css);
636 echo " { display: none !important; }\n}\n";
637 }
638 ?>
639 </style>
640 <script>
641 (function() {
642 var data = <?php echo wp_json_encode($data); ?>;
643 var root = document.getElementById('toplevel_page_phpinfo-wp');
644 if (!root) return;
645 // Hide every submenu <li> that isn't one of the 5 visible group items.
646 // The leaf pages are registered as real submenus (so WP highlights the
647 // toplevel natively) — we just suppress them in the sidebar list.
648 var visible = ['phpinfo-wp','phpinfowp-performance','phpinfowp-audit','phpinfowp-tools','phpinfowp-reports','phpinfowp-license'];
649 <?php if ($is_pro): ?>
650 visible.push('phpinfowp-support');
651 <?php endif; ?>
652 root.querySelectorAll('.wp-submenu li > a').forEach(function(a) {
653 var m = (a.getAttribute('href') || '').match(/[?&]page=([\w-]+)/);
654 if (!m) return;
655 if (visible.indexOf(m[1]) === -1) {
656 a.parentNode.style.display = 'none';
657 }
658 });
659 // Reposition a flyout so it never overflows the viewport. Uses
660 // position:fixed coords (set on .phpinfowp-wpnav-flyout). Anchors
661 // to the right edge of the parent <li>; flips upward when there
662 // isn't enough room below.
663 function position(li, flyout) {
664 var liRect = li.getBoundingClientRect();
665 var vh = window.innerHeight;
666 flyout.style.left = liRect.right + 'px';
667 // Measure natural height by temporarily clearing top + showing
668 var fh = flyout.offsetHeight; // already display:block via :hover
669 var topPreferred = liRect.top - 6; // default: align near top of <li>
670 var maxTop = vh - fh - 8; // keep 8px from bottom edge
671 var minTop = 40; // keep below WP admin bar
672 var top = Math.max(minTop, Math.min(topPreferred, maxTop));
673 flyout.style.top = top + 'px';
674 }
675
676 Object.keys(data).forEach(function(slug) {
677 var grp = data[slug];
678 var anchor = root.querySelector('.wp-submenu a[href*="page=' + slug + '"]');
679 if (!anchor) return;
680 var li = anchor.parentNode;
681 if (li.classList.contains('phpinfowp-has-flyout')) return;
682 li.classList.add('phpinfowp-has-flyout');
683 var flyout = document.createElement('div');
684 flyout.className = 'phpinfowp-wpnav-flyout';
685 var curMatch = location.search.match(/[?&]page=([\w-]+)/);
686 var currentPage = curMatch ? curMatch[1] : '';
687 grp.items.forEach(function(it) {
688 var a = document.createElement('a');
689 a.href = it.url;
690 var pm = it.url.match(/[?&]page=([\w-]+)/);
691 if (pm && pm[1] === currentPage) {
692 a.classList.add('phpinfowp-flyout-active');
693 li.classList.add('current');
694 }
695 var label = document.createElement('span');
696 label.textContent = it.label;
697 a.appendChild(label);
698 if (it.pro) {
699 var badge = document.createElement('span');
700 badge.className = 'phpinfowp-wpnav-flyout-pro';
701 badge.textContent = 'PRO';
702 a.appendChild(badge);
703 }
704 flyout.appendChild(a);
705 });
706 li.appendChild(flyout);
707
708 // Reposition on hover (and again on window resize while open
709 // so a viewport change doesn't strand the flyout off-screen).
710 var isHovered = false;
711 li.addEventListener('mouseenter', function() {
712 isHovered = true;
713 position(li, flyout);
714 });
715 li.addEventListener('mouseleave', function() {
716 isHovered = false;
717 });
718 li.addEventListener('focusin', function() { position(li, flyout); });
719 window.addEventListener('resize', function() {
720 if (isHovered || li.contains(document.activeElement)) position(li, flyout);
721 });
722 });
723 })();
724 </script>
725 <?php
726 }
727
728 // Body classes for layout:
729 // .phpinfowp-page — on every plugin admin page (topbar + margins)
730 // .phpinfowp-has-sidenav — only on grouped pages (Audit/Tools/Reports)
731 public function admin_body_class(string $classes): string {
732 if ($this->is_plugin_page()) {
733 $classes .= ' phpinfowp-page';
734 }
735 if (Phpinfo_WP_Admin_Nav::is_group_page()) {
736 $classes .= ' phpinfowp-has-sidenav';
737 }
738 return $classes;
739 }
740
741 // Highlight the correct group submenu item (Audit/Tools/Reports) when on
742 // any of its leaf pages. Returning the group's slug tells WP which
743 // submenu <li> to mark current. ?string in/out — WP can pass null.
744 public function fix_submenu_file(?string $submenu_file): ?string {
745 $page = sanitize_key($_GET['page'] ?? '');
746 $info = Phpinfo_WP_Admin_Nav::find($page);
747 if ($info) return $info['group_info']['slug'];
748 return $submenu_file;
749 }
750
751 // True when we're on any phpinfo() WP admin page (toplevel, group, or
752 // hidden leaf). Cheap — just inspects $_GET['page'].
753 private function is_plugin_page(): bool {
754 $page = sanitize_key($_GET['page'] ?? '');
755 return $page === 'phpinfo-wp' || strncmp($page, 'phpinfowp-', strlen('phpinfowp-')) === 0;
756 }
757
758 // Render the dark topbar above every plugin page: logo + plugin name +
759 // current page title (left), Pro CTA / status (right). Hooked to
760 // in_admin_header so it appears above #wpbody-content's normal content.
761 public function render_topbar(): void {
762 if (!$this->is_plugin_page()) return;
763 $page = sanitize_key($_GET['page'] ?? '');
764 $title = $this->resolve_page_title($page);
765 $is_pro = Phpinfo_WP_License::is_valid();
766 ?>
767 <div class="phpinfowp-topbar" role="banner">
768 <div class="phpinfowp-topbar-brand">
769 <span class="phpinfowp-topbar-logo" aria-hidden="true">
770 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#ffffff" width="22" height="22">
771 <path d="M7.4 2.6 C 3.4 6.2 3.4 13.8 7.4 17.4 L 9 16 C 5.8 12.8 5.8 7.2 9 4 Z"/>
772 <path d="M12.6 2.6 C 16.6 6.2 16.6 13.8 12.6 17.4 L 11 16 C 14.2 12.8 14.2 7.2 11 4 Z"/>
773 <circle cx="10" cy="10" r="1.6"/>
774 </svg>
775 </span>
776 <span class="phpinfowp-topbar-name">phpinfo() WP</span>
777 <?php if ($title): ?>
778 <span class="phpinfowp-topbar-sep" aria-hidden="true">/</span>
779 <span class="phpinfowp-topbar-title"><?php echo esc_html($title); ?></span>
780 <?php endif; ?>
781 </div>
782 <div class="phpinfowp-topbar-actions">
783 <?php if ($is_pro): ?>
784 <span class="phpinfowp-topbar-pro-active">&#10003; Pro active</span>
785 <?php else: ?>
786 <a href="https://exeebit.com/phpinfo-wp#pricing" target="_blank" rel="noopener"
787 class="phpinfowp-topbar-cta">Unlock Pro &rarr;</a>
788 <?php endif; ?>
789 </div>
790 </div>
791 <?php
792 }
793
794 private function resolve_page_title(string $page): string {
795 if ($page === 'phpinfo-wp') return 'Dashboard';
796 if ($page === 'phpinfowp-license') return 'License';
797 $info = Phpinfo_WP_Admin_Nav::find($page);
798 if ($info) return $info['tab_info']['label'];
799 // Group landing slugs like phpinfowp-audit / -tools / -reports.
800 foreach (Phpinfo_WP_Admin_Nav::groups() as $g) {
801 if ($g['slug'] === $page) return $g['label'];
802 }
803 return '';
804 }
805
806 public function custom_admin_footer_text(string $text): string {
807 if (!$this->is_plugin_page()) {
808 return $text;
809 }
810
811 $logo_svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#777BB3" width="16" height="16" style="vertical-align:-3px;display:inline-block;margin-right:5px;" aria-hidden="true"><path d="M7.4 2.6 C 3.4 6.2 3.4 13.8 7.4 17.4 L 9 16 C 5.8 12.8 5.8 7.2 9 4 Z"/><path d="M12.6 2.6 C 16.6 6.2 16.6 13.8 12.6 17.4 L 11 16 C 14.2 12.8 14.2 7.2 11 4 Z"/><circle cx="10" cy="10" r="1.6"/></svg>';
812
813 $is_pro = Phpinfo_WP_License::is_valid();
814 if ($is_pro) {
815 return sprintf(
816 '<span>%s<strong>%s</strong> — <span style="color:#00a32a;font-weight:600;">&#10003; %s</span> %s · <a href="%s" target="_blank" rel="noopener" style="color:#777BB3;text-decoration:none;font-weight:500;">%s</a></span>',
817 $logo_svg,
818 esc_html__('phpinfo() WP Pro', 'phpinfo-wp'),
819 esc_html__('Active & Protected:', 'phpinfo-wp'),
820 esc_html__('Your server health, security headers, and automated audits are running smoothly.', 'phpinfo-wp'),
821 'https://wordpress.org/support/plugin/phpinfo-wp/reviews/#new-post',
822 esc_html__('Leave a �
823
824
825
826
827 Review', 'phpinfo-wp')
828 );
829 }
830
831 return sprintf(
832 '<span>%s<strong><a href="%s" target="_blank" rel="noopener" style="color:#777BB3;font-weight:700;text-decoration:none;">%s</a></strong> %s %s <a href="%s" target="_blank" rel="noopener" style="display:inline-block;margin-left:8px;background:#777BB3;color:#fff;padding:3px 10px;border-radius:4px;font-size:11.5px;font-weight:700;text-decoration:none;letter-spacing:0.2px;box-shadow:0 1px 2px rgba(119,123,179,0.25);">%s &rarr;</a></span>',
833 $logo_svg,
834 'https://exeebit.com/phpinfo-wp#pricing',
835 esc_html__('phpinfo() WP Pro', 'phpinfo-wp'),
836 esc_html__('Boost server speed & audit security.', 'phpinfo-wp'),
837 esc_html__('Unlock 1-Click Fixes, SSL Monitor & White-Label PDF Reports.', 'phpinfo-wp'),
838 'https://exeebit.com/phpinfo-wp#pricing',
839 esc_html__('Buy Pro License', 'phpinfo-wp')
840 );
841 }
842
843 public function custom_update_footer(string $text): string {
844 if (!$this->is_plugin_page()) {
845 return $text;
846 }
847
848 $is_pro = Phpinfo_WP_License::is_valid();
849 $ver = 'v' . PHPINFOWP_VERSION;
850
851 if ($is_pro) {
852 return sprintf(
853 '<span><strong>%s %s</strong> · <span style="color:#00a32a;font-weight:700;">&#10003; %s</span></span>',
854 esc_html__('phpinfo() WP Pro', 'phpinfo-wp'),
855 esc_html($ver),
856 esc_html__('Pro Active', 'phpinfo-wp')
857 );
858 }
859
860 return sprintf(
861 '<span><strong>phpinfo() WP %s</strong> · <a href="%s" target="_blank" rel="noopener" style="color:#777BB3;font-weight:600;text-decoration:none;">⭐ %s</a></span>',
862 esc_html($ver),
863 'https://wordpress.org/support/plugin/phpinfo-wp/reviews/#new-post',
864 esc_html__('Rate 5-Stars on WP.org', 'phpinfo-wp')
865 );
866 }
867
868 public static function thankyou(): void {
869 // Backwards compatibility no-op
870 }
871
872 public function activate(): void {
873 flush_rewrite_rules();
874 Phpinfo_WP_Snapshots::install_table();
875 Phpinfo_WP_License::schedule_remote_check_event();
876 if (!wp_next_scheduled('phpinfowp_weekly_maintenance')) {
877 wp_schedule_event(time() + DAY_IN_SECONDS, 'weekly', 'phpinfowp_weekly_maintenance');
878 }
879 }
880
881 public function deactivate(): void {
882 flush_rewrite_rules();
883 wp_clear_scheduled_hook('phpinfowp_license_ping');
884 wp_clear_scheduled_hook('phpinfowp_weekly_maintenance');
885 // Clean up safemode artifacts — leaving an orphaned mu-plugin behind
886 // could keep filtering plugins even after this one is gone.
887 Phpinfo_WP_Safemode::stop();
888 Phpinfo_WP_Safemode::uninstall_mu_plugin();
889 }
890 }
891
892 $phpinfo_wp = new Phpinfo_wp();
893 $phpinfo_wp->register();
894
895 register_activation_hook(__FILE__, [$phpinfo_wp, 'activate']);
896 register_deactivation_hook(__FILE__, [$phpinfo_wp, 'deactivate']);
897
898 endif;
899