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 / includes / class-admin-nav.php

class-admin-nav.php in phpinfo() WP – Site Health, PHP Compatibility & Server Audit 7.2.6, at includes/class-admin-nav.php

271 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined('ABSPATH') or die('Unauthorized Access');
3
4 /**
5 * Admin navigation — collapses 18+ submenu items into 5 logical groups.
6 *
7 * The sidebar shows only: Dashboard, Audit, Tools, Reports, License.
8 * Within Audit / Tools / Reports, a horizontal tab bar lets users move
9 * between related screens. All legacy slugs (?page=phpinfowp-eol etc.)
10 * still work for backward compatibility — direct links from the admin
11 * bar, dashboard widget, and admin notices keep functioning.
12 */
13 class Phpinfo_WP_Admin_Nav {
14
15 /**
16 * @var mixed[]|null
17 */
18 private static $cache;
19 /**
20 * @var bool
21 */
22 private static $tabs_rendered = false;
23
24 /**
25 * Group definitions: each group has a label, a slug for its sidebar
26 * entry, and an ordered map of tab-slug => tab-info.
27 */
28 public static function groups(): array {
29 if (self::$cache !== null) return self::$cache;
30
31 self::$cache = [
32 'performance' => [
33 'label' => __('Performance', 'phpinfo-wp'),
34 'slug' => 'phpinfowp-performance',
35 'desc' => __('Analyze and optimize speed, caching, and database bottlenecks', 'phpinfo-wp'),
36 'tabs' => [
37 'phpinfowp-config-grader' => ['label' => __('Config Grader', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-chart-bar'],
38 'phpinfowp-opcache' => ['label' => __('OPcache', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-performance'],
39 'phpinfowp-object-cache' => ['label' => __('Object Cache', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-database'],
40 'phpinfowp-db-health' => ['label' => __('Database', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-database'],
41 'phpinfowp-api-monitor' => ['label' => __('API Monitor', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-networking'],
42 ],
43 ],
44 'audit' => [
45 'label' => __('Security & Core', 'phpinfo-wp'),
46 'slug' => 'phpinfowp-audit',
47 'desc' => __('Read-only health checks across PHP, config, security, and infrastructure', 'phpinfo-wp'),
48 'tabs' => [
49 'phpinfowp-eol' => ['label' => __('PHP EOL', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-calendar-alt'],
50 'phpinfowp-compat' => ['label' => __('PHP Compatibility', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-yes-alt'],
51 'phpinfowp-update-audit' => ['label' => __('Update Guard', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-shield'],
52 'phpinfowp-permissions' => ['label' => __('Permissions Audit', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-admin-network'],
53 'phpinfowp-security-headers' => ['label' => __('Security Headers', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-shield-alt'],
54 'phpinfowp-ssl' => ['label' => __('SSL Monitor', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-lock'],
55 ],
56 ],
57 'tools' => [
58 'label' => __('Page Audit Tools', 'phpinfo-wp'),
59 'slug' => 'phpinfowp-tools',
60 'desc' => __('Active operations — edit config, troubleshoot, take snapshots, run diagnostics', 'phpinfo-wp'),
61 'tabs' => [
62 'phpinfowp-viewer' => ['label' => __('phpinfo() Viewer', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-info'],
63 'phpinfowp-htaccess' => ['label' => __('PHP Config Editor', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-editor-code'],
64 'phpinfowp-safemode' => ['label' => __('Troubleshooting', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-sos'],
65 'phpinfowp-info' => ['label' => __('Basic Info', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-clipboard'],
66 'phpinfowp-extensions' => ['label' => __('Extensions', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-admin-plugins'],
67 'phpinfowp-snapshots' => ['label' => __('Config Snapshots', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-camera'],
68 ],
69 ],
70 'reports' => [
71 'label' => __('Reports & Logs', 'phpinfo-wp'),
72 'slug' => 'phpinfowp-reports',
73 'desc' => __('Monitoring tools, logs, and outbound alerts', 'phpinfo-wp'),
74 'tabs' => [
75 'phpinfowp-log' => ['label' => __('Activity Log', 'phpinfo-wp'), 'pro' => false, 'icon' => 'dashicons-list-view'],
76 'phpinfowp-report' => ['label' => __('Audit Report', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-media-document'],
77 'phpinfowp-cron' => ['label' => __('WP-Cron Monitor', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-clock'],
78 'phpinfowp-mail' => ['label' => __('Mail', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-email-alt'],
79 'phpinfowp-alerts' => ['label' => __('Alerts', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-bell'],
80 'phpinfowp-error-log' => ['label' => __('Error Log', 'phpinfo-wp'), 'pro' => true, 'icon' => 'dashicons-warning'],
81 ],
82 ],
83 ];
84 return self::$cache;
85 }
86
87 /**
88 * Look up which group/tab a slug belongs to. Returns
89 * ['group' => key, 'group_info' => array, 'tab' => slug, 'tab_info' => array]
90 * or null if the slug isn't in any group.
91 */
92 public static function find(string $slug): ?array {
93 foreach (self::groups() as $key => $group) {
94 if (isset($group['tabs'][$slug])) {
95 return [
96 'group' => $key,
97 'group_info' => $group,
98 'tab' => $slug,
99 'tab_info' => $group['tabs'][$slug],
100 ];
101 }
102 }
103 return null;
104 }
105
106 /**
107 * The first tab in a group — used as the default when the user lands
108 * on the group page without a specific tab.
109 */
110 public static function first_tab(string $group_key): ?string {
111 $g = self::groups()[$group_key] ?? null;
112 if (!$g) return null;
113 return array_key_first($g['tabs']);
114 }
115
116 /**
117 * Render the vertical secondary sidebar for the current screen. Pass the
118 * current page slug; if omitted we read it from $_GET['page']. Silently
119 * no-ops if the slug isn't part of any group.
120 *
121 * Layout mirrors Elementor's in-page secondary nav: a fixed-width column
122 * on the left of #wpbody-content with grouped vertical links. CSS pushes
123 * the page's .wrap to the right via a body class added in admin_body_class.
124 */
125 public static function render_tabs(?string $current_slug = null): void {
126 // Idempotent — render_group() calls this, then dispatches to a view
127 // file that may also call it. Render only once per request.
128 if (self::$tabs_rendered) return;
129
130 $current_slug = $current_slug ?? self::current_tab_slug() ?? sanitize_key($_GET['page'] ?? '');
131 $info = self::find($current_slug);
132 if (!$info) return;
133
134 self::$tabs_rendered = true;
135
136 $is_pro = Phpinfo_WP_License::is_valid();
137 $group = $info['group_info'];
138
139 echo '<aside class="phpinfowp-side-nav" role="navigation" aria-label="' . esc_attr($group['label']) . '">';
140 echo '<ul class="phpinfowp-side-nav-list">';
141 foreach ($group['tabs'] as $slug => $tab) {
142 $is_active = $slug === $current_slug;
143 $url = admin_url('admin.php?page=' . $slug);
144 $classes = 'phpinfowp-side-nav-item' . ($is_active ? ' is-active' : '');
145 $icon = !empty($tab['icon']) ? $tab['icon'] : 'dashicons-admin-generic';
146 $badge = (!$is_pro && !empty($tab['pro']))
147 ? '<span class="phpinfowp-side-nav-pro">PRO</span>'
148 : '';
149 echo '<li><a href="' . esc_url($url) . '" class="' . esc_attr($classes) . '">';
150 echo '<span class="dashicons ' . esc_attr($icon) . ' phpinfowp-side-nav-icon" aria-hidden="true"></span>';
151 echo '<span class="phpinfowp-side-nav-label-text">' . esc_html($tab['label']) . '</span>' . $badge;
152 echo '</a></li>';
153 }
154 echo '</ul>';
155 echo '</aside>';
156 }
157
158 /**
159 * Render the group landing page. The tab bar itself is injected by the
160 * admin_notices hook (see auto_render below) so it appears above every
161 * grouped page — not just the group landing. Here we just dispatch to
162 * the active tab's view file.
163 */
164 public static function render_group(string $group_key): void {
165 $groups = self::groups();
166 if (!isset($groups[$group_key])) wp_die(__('Unknown group.', 'phpinfo-wp'));
167
168 $tab = self::resolve_group_tab($group_key);
169
170 // Briefly impersonate the tab slug so the dispatched view's internal
171 // POST handlers and "is this my page?" checks work unchanged.
172 $original_page = $_GET['page'] ?? '';
173 $_GET['page'] = $tab;
174 self::dispatch_view($tab);
175 $_GET['page'] = $original_page;
176 }
177
178 /**
179 * Resolve which tab is active when on a group landing page. Reads ?tab=
180 * if present and valid; otherwise returns the first tab.
181 */
182 public static function resolve_group_tab(string $group_key): string {
183 $g = self::groups()[$group_key] ?? null;
184 if (!$g) return '';
185 $req = sanitize_key($_GET['tab'] ?? '');
186 return isset($g['tabs'][$req]) ? $req : array_key_first($g['tabs']);
187 }
188
189 /**
190 * Map group-landing slugs back to their group key. Used by auto_render
191 * to know "if user is on phpinfowp-performance or phpinfowp-audit, what group is that?"
192 */
193 public static function group_for_slug(string $slug): ?string {
194 $m = [
195 'phpinfowp-performance' => 'performance',
196 'phpinfowp-audit' => 'audit',
197 'phpinfowp-tools' => 'tools',
198 'phpinfowp-reports' => 'reports',
199 ];
200 return $m[$slug] ?? null;
201 }
202
203 /**
204 * Hook target — emits the tab bar at the very top of every grouped page,
205 * including direct legacy slugs and the new group landing slugs.
206 */
207 public static function auto_render(): void {
208 $tab = self::current_tab_slug();
209 if (!$tab) return;
210
211 // admin_notices fires above the page's .wrap. The sidebar is
212 // position:absolute via CSS, so it lifts out of normal flow and the
213 // body-class added in admin_body_class pushes .wrap right to make room.
214 self::render_tabs($tab);
215 }
216
217 /**
218 * True when the current admin page belongs to a grouped phpinfo screen
219 * (so it needs the secondary sidebar layout). Used by admin_body_class.
220 */
221 public static function is_group_page(): bool {
222 return self::current_tab_slug() !== null;
223 }
224
225 /**
226 * Resolve the active tab slug for the current request, or null if the
227 * current page isn't part of any group.
228 */
229 private static function current_tab_slug(): ?string {
230 $page = sanitize_key($_GET['page'] ?? '');
231 if (!$page || strncmp($page, 'phpinfo', strlen('phpinfo')) !== 0) return null;
232 if (self::find($page)) return $page;
233 $group_key = self::group_for_slug($page);
234 return $group_key ? self::resolve_group_tab($group_key) : null;
235 }
236
237 /**
238 * Slug → view file mapping. Mirrors the require statements that the
239 * Phpinfo_wp view_* methods used to do, keeping a single source of truth.
240 */
241 private static function dispatch_view(string $slug): void {
242 $map = [
243 'phpinfowp-config-grader' => 'views/pro/config-grader.php',
244 'phpinfowp-eol' => 'views/pro/eol.php',
245 'phpinfowp-compat' => 'views/pro/compat.php',
246 'phpinfowp-security-headers' => 'views/pro/security-headers.php',
247 'phpinfowp-ssl' => 'views/pro/ssl.php',
248 'phpinfowp-opcache' => 'views/pro/opcache.php',
249 'phpinfowp-object-cache' => 'views/pro/object-cache.php',
250 'phpinfowp-db-health' => 'views/pro/db-health.php',
251 'phpinfowp-api-monitor' => 'views/pro/api-monitor.php',
252 'phpinfowp-permissions' => 'views/pro/permissions.php',
253 'phpinfowp-htaccess' => 'views/htaccess.php',
254 'phpinfowp-safemode' => 'views/safemode.php',
255 'phpinfowp-viewer' => 'views/phpinfo.php',
256 'phpinfowp-extensions' => 'views/extension.php',
257 'phpinfowp-info' => 'views/info.php',
258 'phpinfowp-snapshots' => 'views/pro/snapshots.php',
259 'phpinfowp-cron' => 'views/pro/cron.php',
260 'phpinfowp-mail' => 'views/pro/mail.php',
261 'phpinfowp-error-log' => 'views/pro/error-log.php',
262 'phpinfowp-log' => 'views/log.php',
263 'phpinfowp-report' => 'views/pro/report.php',
264 'phpinfowp-alerts' => 'views/pro/alerts.php',
265 ];
266 $file = $map[$slug] ?? null;
267 if (!$file) { echo '<p>' . __('Unknown view.', 'phpinfo-wp') . '</p>'; return; }
268 require PHPINFOWP_DIR . $file;
269 }
270 }
271