PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.10
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.10
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / includes / class-metasync-admin-navigation.php

class-metasync-admin-navigation.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.10, at includes/class-metasync-admin-navigation.php

1,856 lines 94.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 /**
7 * Admin navigation header, nav tabs, and admin-bar status indicator.
8 *
9 * Extracted from Metasync_Admin to keep the admin class focused on
10 * non-UI concerns. All header/nav rendering and admin-bar badge
11 * logic lives here.
12 *
13 * @package Metasync
14 * @subpackage Metasync/includes
15 */
16 class Metasync_Admin_Navigation
17 {
18 /** @var self|null */
19 private static $instance = null;
20
21 const CACHE_KEY = 'metasync_admin_bar_status';
22
23 /**
24 * Get singleton instance.
25 *
26 * @return self
27 */
28 public static function instance()
29 {
30 if (self::$instance === null) {
31 self::$instance = new self();
32 }
33 return self::$instance;
34 }
35
36 private function __construct() {}
37
38 // ------------------------------------------------------------------
39 // Logo resolution helper
40 // ------------------------------------------------------------------
41
42 /**
43 * Resolve which logo(s) to display based on whitelabel settings.
44 *
45 * @return array{show_logo: bool, use_dual: bool, url: string, light_url: string, dark_url: string, is_default: bool}
46 */
47 private function resolve_logo_data()
48 {
49 $wl_settings = Metasync::get_whitelabel_settings();
50 $is_whitelabel = !empty($wl_settings['is_whitelabel']);
51
52 $light_raw = Metasync::get_whitelabel_logo_light();
53 $dark_raw = Metasync::get_whitelabel_logo_dark();
54 $has_light = !empty($light_raw) && filter_var($light_raw, FILTER_VALIDATE_URL);
55 $has_dark = !empty($dark_raw) && filter_var($dark_raw, FILTER_VALIDATE_URL);
56 $use_dual = ($has_light && $has_dark && $light_raw !== $dark_raw);
57
58 $data = [
59 'show_logo' => false,
60 'use_dual' => false,
61 'url' => '',
62 'light_url' => '',
63 'dark_url' => '',
64 'is_default' => false,
65 ];
66
67 if ($use_dual) {
68 $data['show_logo'] = true;
69 $data['use_dual'] = true;
70 $data['light_url'] = $light_raw;
71 $data['dark_url'] = $dark_raw;
72 } else {
73 $single = $has_light ? $light_raw : ($has_dark ? $dark_raw : null);
74 if ($single) {
75 $data['show_logo'] = true;
76 $data['url'] = $single;
77 } elseif (!$is_whitelabel) {
78 $data['show_logo'] = true;
79 $data['url'] = Metasync::HOMEPAGE_DOMAIN . '/wp-content/uploads/2023/12/white.svg';
80 $data['is_default'] = true;
81 }
82 }
83
84 return $data;
85 }
86
87 // ------------------------------------------------------------------
88 // Header + Nav rendering
89 // ------------------------------------------------------------------
90
91 /**
92 * Render the standard header followed by the navigation tabs.
93 */
94 public function render_standard_header_nav($page_title = null, $current_page = null)
95 {
96 $this->render_static_header($page_title);
97 $this->render_static_navigation($current_page);
98 }
99
100 /**
101 * Render the page header (logo, connection badge, theme toggle).
102 */
103 public function render_static_header($page_title = null)
104 {
105 $effective_plugin_name = Metasync::get_effective_plugin_name();
106 $display_title = $page_title ?: $effective_plugin_name;
107 $logo = $this->resolve_logo_data();
108
109 $current_theme = get_option('metasync_theme', 'dark');
110 $general_settings = Metasync::get_option('general');
111 $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
112 $is_integrated = !empty($searchatlas_api_key);
113 ?>
114 <div class="metasync-header" data-current-theme="<?php echo esc_attr($current_theme); ?>">
115 <div class="metasync-header-left">
116 <?php if ($logo['show_logo'] && $logo['use_dual']): ?>
117 <div class="metasync-logo-container">
118 <img src="<?php echo esc_url($logo['light_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-light" />
119 <img src="<?php echo esc_url($logo['dark_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-dark" />
120 </div>
121 <?php elseif ($logo['show_logo'] && !empty($logo['url'])): ?>
122 <div class="metasync-logo-container">
123 <img src="<?php echo esc_url($logo['url']); ?>" alt="Logo" class="metasync-logo<?php echo !empty($logo['is_default']) ? ' metasync-logo-default' : ''; ?>" />
124 </div>
125 <?php endif; ?>
126 </div>
127 <div class="metasync-header-right">
128 <div class="metasync-status <?php echo $is_integrated ? 'connected' : 'disconnected'; ?>">
129 <span class="status-dot"></span>
130 <span class="status-text"><?php echo $is_integrated ? 'Connected' : 'Not Connected'; ?></span>
131 </div>
132 <button type="button" class="metasync-theme-toggle" onclick="toggleMetasyncTheme()" title="Toggle theme">
133 <span class="theme-icon-light">&#9728;</span>
134 <span class="theme-icon-dark">&#9790;</span>
135 </button>
136 </div>
137 </div>
138 <?php
139 }
140
141 /**
142 * Render the horizontal navigation tabs.
143 */
144 public function render_static_navigation($current_page = null)
145 {
146 $general_options = Metasync::get_option('general') ?? [];
147 $whitelabel_settings = Metasync::get_whitelabel_settings();
148 $page_slug = Metasync_Admin::$page_slug;
149
150 $menu_items = [];
151 $menu_icons = [
152 'dashboard' => 'dashboard',
153 'seo_controls' => 'search',
154 'optimal_settings' => 'superhero-alt',
155 'instant_index' => 'performance',
156 'google_console' => 'chart-area',
157 'compatibility' => 'admin-tools',
158 'sync_log' => 'list-view',
159 'redirections' => 'undo',
160 'robots_txt' => 'shield',
161 'xml_sitemap' => 'networking',
162 'custom_pages' => 'admin-page',
163 'report_issue' => 'sos',
164 'general' => 'admin-settings',
165 ];
166
167 if (empty($whitelabel_settings['hide_dashboard'])) {
168 $menu_items['dashboard'] = ['title' => 'Dashboard', 'slug_suffix' => '-dashboard'];
169 }
170
171 if (empty($whitelabel_settings['hide_indexation_control'])) {
172 $menu_items['seo_controls'] = ['title' => 'Indexation Control', 'slug_suffix' => '-seo-controls'];
173 }
174
175 if ($general_options['enable_optimal_settings'] ?? false) {
176 $menu_items['optimal_settings'] = ['title' => 'Optimal Settings', 'slug_suffix' => '-optimal-settings'];
177 }
178
179 if ($general_options['enable_googleinstantindex'] ?? false) {
180 $menu_items['instant_index'] = ['title' => 'Instant Indexing', 'slug_suffix' => '-instant-index'];
181 }
182
183 if ($general_options['enable_google_console'] ?? false) {
184 $menu_items['google_console'] = ['title' => 'Google Console', 'slug_suffix' => '-google-console'];
185 }
186
187 if (empty($whitelabel_settings['hide_compatibility'])) {
188 $menu_items['compatibility'] = ['title' => 'Compatibility', 'slug_suffix' => '-compatibility'];
189 }
190
191 if (empty($whitelabel_settings['hide_sync_log'])) {
192 $menu_items['sync_log'] = ['title' => 'Changes Log', 'slug_suffix' => '-sync-log'];
193 }
194
195 if (empty($whitelabel_settings['hide_redirections'])) {
196 $menu_items['redirections'] = ['title' => 'Redirections', 'slug_suffix' => '-redirections'];
197 }
198
199 if (empty($whitelabel_settings['hide_robots'])) {
200 $menu_items['robots_txt'] = ['title' => 'Robots.txt', 'slug_suffix' => '-robots-txt'];
201 }
202
203 $menu_items['xml_sitemap'] = ['title' => 'XML Sitemap', 'slug_suffix' => '-xml-sitemap'];
204 ?>
205 <div class="metasync-nav-wrapper">
206 <div class="metasync-nav-tabs">
207 <div class="metasync-nav-left">
208 <?php foreach ($menu_items as $key => $menu_item):
209 $is_active = ($current_page === $key);
210 $icon = $menu_icons[$key] ?? 'admin-generic';
211 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
212 ?>
213 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
214 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
215 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
216 </a>
217 <?php endforeach; ?>
218 </div>
219 <div class="metasync-nav-right">
220 <a href="?page=<?php echo esc_attr( $page_slug ); ?>-custom-pages" class="metasync-nav-tab <?php echo $current_page === 'custom_pages' ? 'active' : ''; ?>" style="margin-right: 10px;">
221 <span class="tab-icon"><span class="dashicons dashicons-admin-page"></span></span>
222 <span class="tab-text">Custom Pages</span>
223 </a>
224 <a href="?page=<?php echo esc_attr( $page_slug ); ?>-report-issue" class="metasync-nav-tab <?php echo $current_page === 'report_issue' ? 'active' : ''; ?>">
225 <span class="tab-icon"><span class="dashicons dashicons-sos"></span></span>
226 <span class="tab-text">Report Issue</span>
227 </a>
228 <div class="metasync-simple-dropdown">
229 <button type="button" class="metasync-seo-btn" id="metasync-seo-btn" onclick="toggleSeoMenuPortal(event)" aria-expanded="false">
230 <span class="tab-icon"><span class="dashicons dashicons-search"></span></span>
231 <span class="tab-text">SEO</span>
232 <span class="dropdown-arrow"></span>
233 </button>
234 </div>
235 <div class="metasync-simple-dropdown">
236 <button type="button" class="metasync-settings-btn" id="metasync-settings-btn" onclick="toggleSettingsMenuPortal(event)" aria-expanded="false">
237 <span class="tab-icon"><span class="dashicons dashicons-admin-settings"></span></span>
238 <span class="tab-text">Settings</span>
239 <span class="dropdown-arrow"></span>
240 </button>
241 </div>
242 </div>
243 </div>
244 </div>
245
246 <script>
247 function toggleSeoMenuPortal(event) {
248 event.preventDefault();
249 event.stopPropagation();
250 var button = event.currentTarget;
251 var existingMenu = document.getElementById('metasync-seo-portal-menu');
252 if (existingMenu) {
253 existingMenu.remove();
254 button.classList.remove('active');
255 button.setAttribute('aria-expanded', 'false');
256 return;
257 }
258 var menu = document.createElement('div');
259 menu.id = 'metasync-seo-portal-menu';
260 menu.className = 'metasync-portal-menu';
261
262 var pageSlug = '<?php echo esc_js( $page_slug ); ?>';
263 var seoLinks = [
264 { href: '?page=' + pageSlug + '-seo-controls', text: 'Indexation Control' },
265 { href: '?page=' + pageSlug + '-xml-sitemap', text: 'XML Sitemap' },
266 { href: '?page=' + pageSlug + '-robots-txt', text: 'Robots.txt' },
267 { href: '?page=' + pageSlug + '-redirections', text: 'Redirections' },
268 ];
269 seoLinks.forEach(function(item) {
270 var link = document.createElement('a');
271 link.href = item.href;
272 link.className = 'metasync-portal-item';
273 link.textContent = item.text;
274 menu.appendChild(link);
275 });
276
277 var rect = button.getBoundingClientRect();
278 menu.style.position = 'fixed';
279 menu.style.top = (rect.bottom + 8) + 'px';
280 menu.style.right = (window.innerWidth - rect.right) + 'px';
281 menu.style.zIndex = '999999999';
282 document.body.appendChild(menu);
283 button.classList.add('active');
284 button.setAttribute('aria-expanded', 'true');
285 }
286
287 function toggleSettingsMenuPortal(event) {
288 event.preventDefault();
289 event.stopPropagation();
290 var button = event.currentTarget;
291 var existingMenu = document.getElementById('metasync-portal-menu');
292 if (existingMenu) {
293 existingMenu.remove();
294 button.classList.remove('active');
295 button.setAttribute('aria-expanded', 'false');
296 return;
297 }
298 var menu = document.createElement('div');
299 menu.id = 'metasync-portal-menu';
300 menu.className = 'metasync-portal-menu';
301
302 var hideAdvanced = <?php echo !empty($whitelabel_settings['hide_advanced']) ? 'true' : 'false'; ?>;
303 var showGeneral = <?php echo Metasync_Access_Control::user_can_access('hide_settings') ? 'true' : 'false'; ?>;
304
305 if (showGeneral) {
306 var generalLink = document.createElement('a');
307 generalLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=general';
308 generalLink.className = 'metasync-portal-item';
309 generalLink.textContent = 'General';
310 menu.appendChild(generalLink);
311 }
312
313 var whitelabelLink = document.createElement('a');
314 whitelabelLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=whitelabel';
315 whitelabelLink.className = 'metasync-portal-item';
316 whitelabelLink.textContent = 'White Label';
317 menu.appendChild(whitelabelLink);
318
319 if (!hideAdvanced) {
320 var advancedLink = document.createElement('a');
321 advancedLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=advanced';
322 advancedLink.className = 'metasync-portal-item';
323 advancedLink.textContent = 'Advanced';
324 menu.appendChild(advancedLink);
325 }
326
327
328 var rect = button.getBoundingClientRect();
329 menu.style.position = 'fixed';
330 menu.style.top = (rect.bottom + 8) + 'px';
331 menu.style.right = (window.innerWidth - rect.right) + 'px';
332 menu.style.zIndex = '999999999';
333 document.body.appendChild(menu);
334 button.classList.add('active');
335 button.setAttribute('aria-expanded', 'true');
336 }
337 document.addEventListener('click', function(event) {
338 var seoButton = document.getElementById('metasync-seo-btn');
339 var seoMenu = document.getElementById('metasync-seo-portal-menu');
340 if (seoMenu && seoButton && !seoButton.contains(event.target) && !seoMenu.contains(event.target)) {
341 seoMenu.remove();
342 seoButton.classList.remove('active');
343 seoButton.setAttribute('aria-expanded', 'false');
344 }
345
346 var button = document.getElementById('metasync-settings-btn');
347 var menu = document.getElementById('metasync-portal-menu');
348 if (menu && button && !button.contains(event.target) && !menu.contains(event.target)) {
349 menu.remove();
350 button.classList.remove('active');
351 button.setAttribute('aria-expanded', 'false');
352 }
353 });
354 </script>
355 <?php
356 }
357
358 // ------------------------------------------------------------------
359 // Menu registration + enhanced navigation (moved from Metasync_Admin)
360 // ------------------------------------------------------------------
361
362 /**
363 * Helper method to get available menu items based on configuration.
364 * Items are grouped into 'seo' (SEO Features) and 'plugin' (Plugin) categories.
365 */
366 public function get_available_menu_items()
367 {
368 $general_options = Metasync::get_option('general') ?? [];
369 $has_api_key = !empty($general_options['searchatlas_api_key'] ?? '');
370 $has_uuid = !empty($general_options['otto_pixel_uuid'] ?? '');
371 $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options);
372
373 $menu_items = [];
374
375 // === SEO FEATURES GROUP ===
376
377 // Dashboard (check access control)
378 if (Metasync_Access_Control::user_can_access('hide_dashboard')) {
379 $menu_items['dashboard'] = [
380 'title' => 'Dashboard',
381 'slug_suffix' => '-dashboard',
382 'callback' => 'create_admin_dashboard_iframe',
383 'internal_nav' => 'Dashboard',
384 'group' => 'seo'
385 ];
386 }
387
388 // Indexation Control (check access control)
389 if (Metasync_Access_Control::user_can_access('hide_indexation_control')) {
390 $menu_items['seo_controls'] = [
391 'title' => 'Indexation',
392 'slug_suffix' => '-seo-controls',
393 'callback' => 'create_admin_seo_controls_page',
394 'internal_nav' => 'Indexation Control',
395 'group' => 'seo'
396 ];
397 }
398
399 // Instant Indexing - setting now stored in seo_controls (moved from Settings to Indexation Control)
400 $seo_controls = Metasync::get_option('seo_controls');
401 if ($seo_controls['enable_googleinstantindex'] ?? false) {
402 $menu_items['instant_index'] = [
403 'title' => 'Instant Indexing',
404 'slug_suffix' => '-instant-index',
405 'callback' => 'create_admin_google_instant_index_page',
406 'internal_nav' => 'Instant Indexing',
407 'group' => 'seo'
408 ];
409 }
410
411 if ($general_options['enable_google_console'] ?? false) {
412 $menu_items['google_console'] = [
413 'title' => 'Google Console',
414 'slug_suffix' => '-google-console',
415 'callback' => 'create_admin_google_console_page',
416 'internal_nav' => 'Google Console',
417 'group' => 'seo'
418 ];
419 }
420
421 if ($general_options['enable_bing_console'] ?? false) {
422 $menu_items['bing_console'] = [
423 'title' => 'Bing Console',
424 'slug_suffix' => '-bing-console',
425 'callback' => 'create_admin_bing_console_page',
426 'internal_nav' => 'Bing Console',
427 'group' => 'seo'
428 ];
429 }
430
431 // Redirections page (check access control)
432 if (Metasync_Access_Control::user_can_access('hide_redirections')) {
433 $menu_items['redirections'] = [
434 'title' => 'Redirections',
435 'slug_suffix' => '-redirections',
436 'callback' => 'create_admin_redirections_page',
437 'internal_nav' => 'Redirections',
438 'group' => 'seo'
439 ];
440 }
441
442 // Robots.txt page (check access control)
443 if (Metasync_Access_Control::user_can_access('hide_robots')) {
444 $menu_items['robots_txt'] = [
445 'title' => 'Robots.txt',
446 'slug_suffix' => '-robots-txt',
447 'callback' => 'create_admin_robots_txt_page',
448 'internal_nav' => 'Robots.txt',
449 'group' => 'seo'
450 ];
451 }
452
453 // XML Sitemap page (check access control)
454 if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) {
455 $menu_items['xml_sitemap'] = [
456 'title' => 'XML Sitemap',
457 'slug_suffix' => '-xml-sitemap',
458 'callback' => 'create_admin_xml_sitemap_page',
459 'internal_nav' => 'XML Sitemap',
460 'group' => 'seo'
461 ];
462 }
463
464 // Schema Markup
465 $menu_items['schema_markup'] = [
466 'title' => 'Schema Markup',
467 'slug_suffix' => '-schema-markup',
468 'callback' => 'create_admin_schema_markup_page',
469 'internal_nav' => 'Schema Markup',
470 'group' => 'seo'
471 ];
472
473 // 404 Monitor (always available — SEO monitoring tool)
474 $menu_items['monitor_404'] = [
475 'title' => '404 Monitor',
476 'slug_suffix' => '-404-monitor',
477 'callback' => 'create_admin_404_monitor_page',
478 'internal_nav' => '404 Monitor',
479 'group' => 'seo'
480 ];
481
482 // Site Verification
483 $menu_items['site_verification'] = [
484 'title' => 'Site Verification',
485 'slug_suffix' => '-search-engine-verify',
486 'callback' => 'create_admin_search_engine_verification_page',
487 'internal_nav' => 'Site Verification',
488 'group' => 'seo'
489 ];
490
491 // SEO Health dashboard (always available)
492 $menu_items['seo_health'] = [
493 'title' => 'SEO Health',
494 'slug_suffix' => '-seo-health',
495 'callback' => 'create_admin_seo_health_page',
496 'internal_nav' => 'SEO Health',
497 'group' => 'seo'
498 ];
499
500 // Import SEO Data page (check access control)
501 if (Metasync_Access_Control::user_can_access('hide_import_seo')) {
502 $menu_items['import_seo'] = [
503 'title' => 'Import SEO Data',
504 'slug_suffix' => '-import-external',
505 'callback' => 'render_import_external_data_page',
506 'internal_nav' => 'Import SEO Data',
507 'group' => 'seo'
508 ];
509 }
510
511 // === PLUGIN GROUP ===
512
513 // Settings (check access control)
514 if (Metasync_Access_Control::user_can_access('hide_settings')) {
515 $menu_items['general'] = [
516 'title' => 'Settings',
517 'slug_suffix' => '',
518 'callback' => 'create_admin_settings_page',
519 'internal_nav' => 'General Settings',
520 'group' => 'plugin'
521 ];
522 }
523
524 // Local Business
525 $menu_items['local_business'] = [
526 'title' => 'Local Business',
527 'slug_suffix' => '-local-business',
528 'callback' => 'create_admin_local_business_page',
529 'internal_nav' => 'Local Business',
530 'group' => 'seo'
531 ];
532
533 // Breadcrumbs
534 $menu_items['breadcrumbs'] = [
535 'title' => 'Breadcrumbs',
536 'slug_suffix' => '-breadcrumbs',
537 'callback' => 'create_admin_breadcrumbs_page',
538 'internal_nav' => 'Breadcrumbs',
539 'group' => 'seo'
540 ];
541
542 // Code Snippets
543 $menu_items['code_snippets'] = [
544 'title' => 'Code Snippets',
545 'slug_suffix' => '-code-snippets',
546 'callback' => 'create_admin_code_snippets_page',
547 'internal_nav' => 'Code Snippets',
548 'group' => 'plugin'
549 ];
550
551 // Code Minification
552 $menu_items['code_minification'] = [
553 'title' => 'Code Minification',
554 'slug_suffix' => '-code-minification',
555 'callback' => 'create_admin_code_minification_page',
556 'internal_nav' => 'Code Minification',
557 'group' => 'plugin'
558 ];
559
560 // Media Optimization
561 $menu_items['media_optimization'] = [
562 'title' => 'Media Optimization',
563 'slug_suffix' => '-media-optimization',
564 'callback' => 'create_admin_media_optimization_page',
565 'internal_nav' => 'Media Optimization',
566 'group' => 'plugin'
567 ];
568
569 // Custom HTML Pages (check access control)
570 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
571 $menu_items['custom_pages'] = [
572 'title' => 'Custom Pages',
573 'slug_suffix' => '-custom-pages',
574 'callback' => 'create_admin_custom_pages_page',
575 'internal_nav' => 'Custom HTML Pages',
576 'group' => 'plugin'
577 ];
578 }
579
580 if ($general_options['enable_optimal_settings'] ?? false) {
581 $menu_items['optimal_settings'] = [
582 'title' => 'Optimal Settings',
583 'slug_suffix' => '-optimal-settings',
584 'callback' => 'create_admin_optimal_settings_page',
585 'internal_nav' => 'Optimal Settings',
586 'group' => 'plugin'
587 ];
588 }
589
590 // Compatibility page (check access control)
591 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
592 $menu_items['compatibility'] = [
593 'title' => 'Compatibility',
594 'slug_suffix' => '-compatibility',
595 'callback' => 'create_admin_compatibility_page',
596 'internal_nav' => 'Compatibility',
597 'group' => 'plugin'
598 ];
599 }
600
601 // Sync Log page (check access control)
602 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
603 $menu_items['sync_log'] = [
604 'title' => 'Changes Log',
605 'slug_suffix' => '-sync-log',
606 'callback' => 'create_admin_sync_log_page',
607 'internal_nav' => 'Changes Log',
608 'group' => 'plugin'
609 ];
610 }
611
612 // Bot Statistics
613 $menu_items['bot_statistics'] = [
614 'title' => 'Bot Statistics',
615 'slug_suffix' => '-bot-statistics',
616 'callback' => 'create_admin_bot_statistics_page',
617 'internal_nav' => 'Bot Statistics',
618 'group' => 'plugin'
619 ];
620
621 // Report Issue page (check access control)
622 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
623 $menu_items['report_issue'] = [
624 'title' => 'Report Issue',
625 'slug_suffix' => '-report-issue',
626 'callback' => 'create_admin_report_issue_page',
627 'internal_nav' => 'Report Issue',
628 'group' => 'plugin'
629 ];
630 }
631
632 return $menu_items;
633 }
634
635 /**
636 * Register all WordPress admin menu / submenu pages.
637 *
638 * @param Metasync_Admin $admin The admin instance whose callbacks WordPress will invoke.
639 */
640 public function add_plugin_settings_page($admin)
641 {
642 if (!Metasync::current_user_has_plugin_access()) {
643 return;
644 }
645
646 $data = Metasync::get_option('general');
647 $plugin_name = Metasync::get_effective_plugin_name();
648 $menu_name = $plugin_name;
649 $menu_title = $plugin_name;
650 $menu_slug = !isset($data['white_label_plugin_menu_slug']) || $data['white_label_plugin_menu_slug'] == "" ? Metasync_Admin::$page_slug : $data['white_label_plugin_menu_slug'];
651 $menu_icon = !isset($data['white_label_plugin_menu_icon']) || $data['white_label_plugin_menu_icon'] == "" ? 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzQiIHZpZXdCb3g9IjAgMCAzMiAzNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE4NzlfMTcyNzcpIj4KPHBhdGggZD0iTTI5LjAyMTUgMi4xNjc2NkwzMC4wMTAxIDIuMTIyNDFMMjkuMTYyNyA4LjcyNzA1TDI5LjExNTcgOC44MTc0M0w1LjI5NTA0IDMzLjI0NTJMNC43MzAxMiAzMy4yOTA1TDQuMDcxMDQgMzMuOTY5MUgxLjk1MjYyTDIuMjM1MDggMzIuMjk1M0wyLjA5Mzg0IDMyLjM0MDZMMi4xODggMzEuNjYySDEuNjIzMDhDMS41NzYgMzEuNjYyIDEuNDgxODUgMzEuNjYyIDEuNDgxODUgMzEuNjE2N0MxLjQzNDc4IDMxLjU3MTYgMS4zODc3IDMxLjQ4MSAxLjM4NzcgMzEuNDM1OEwyLjUxNzU0IDI1LjEwMjdDMi41MTc1NCAyNS4wNTc1IDIuNTY0NiAyNS4wMTIyIDIuNTY0NiAyNS4wMTIyTDI2LjE5NjkgMC42Mjk2MDdDMjYuMjQ0IDAuNTg0MzY2IDI2LjI5MTEgMC41MzkxMjUgMjYuMzM4MSAwLjUzOTEyNUgyOC4zNjI1QzI4LjQwOTUgMC40OTM4ODQgMjguNDU2NiAwLjUzOTEyNSAyOC41MDM3IDAuNTg0MzY2QzI4LjU1MDcgMC42Mjk2MDcgMjguNTk3OSAwLjcyMDA3MSAyOC41OTc5IDAuNzY1MzExTDI4LjUwMzcgMS4zNTMzOUgyOS4xMTU3TDI5LjAyMTUgMi4xNjc2NlpNMS45MDU1NCAzMS4yMDk2SDIuMjgyMTZMMy4yMjM2OCAyNS43ODEySDMuMjcwNzZMNi44MDE1IDIyLjI1MjhMNi44NDg1MSAyMi4yOTgxTDIwLjIxODMgOC40NTU1N0wyNi45MDMxIDEuMzUzMzlIMjguMDMyOUwyOC4wNzk5IDAuOTkxNDk5SDI2LjQ3OTNMMi45ODgzIDI1LjIzODRMMS45MDU1NCAzMS4yMDk2Wk0yOC42OTE5IDguNTQ1OTVMMjkuNDQ1MiAyLjYyMDAxSDI4Ljk3NDVMMjguMzE1MyA3Ljc3Njk2TDI4LjI2ODMgNy44MjIyNEwyOC4xMjcxIDcuOTU3ODlMMjcuOTM4NyA5LjMxNTExTDI4LjY5MTkgOC41NDU5NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0zMS41NTQ0IDE4LjE5MzdDMzEuNzM0OSAxOC42NjYyIDMxLjgwMjggMTkuMjA2NCAzMS42ODk4IDE5Ljc2OUwyOS42NTgyIDMyLjEwMTNIMjkuMDkzOUwyOS4wMjYyIDMyLjQzODhIMjUuOTMzNkwyNi4wNjkxIDMxLjYwNjJIMjYuMDAxM0wyNi4wNjkxIDMxLjI2ODZIMjUuNzk4MUMyNS43NTMgMzEuMjY4NiAyNS43MzA1IDMxLjI2ODYgMjUuNzA3OSAzMS4yNDYxQzI1LjY4NTIgMzEuMjIzNyAyNS42ODUyIDMxLjE3ODYgMjUuNjg1MiAzMS4xNTZMMjYuMzYyNCAyNy4zOThIMTguNDE2N0wxNy40MjM0IDMyLjA3ODdIMTYuODM2NUwxNi43Njg3IDMyLjQxNjNIMTMuNjk4OEwxMy45MDE5IDMxLjU4MzZIMTMuODM0M0wxMy45MDE5IDMxLjI2ODZIMTMuNjMxQzEzLjYwODQgMzEuMjY4NiAxMy41NjMzIDMxLjI2ODYgMTMuNTQwOCAzMS4yNDYxQzEzLjU0MDggMzEuMjAxMSAxMy41MTgyIDMxLjE3ODYgMTMuNTQwOCAzMS4xMzM2TDE2LjM2MjQgMTguOTEzOEMxNi40OTc5IDE4LjM1MTIgMTYuNzQ2MiAxNy44MzM2IDE3LjEyOTkgMTcuMzM4NUMxNy40OTEyIDE2Ljg2NiAxNy45NDI2IDE2LjQ4MzUgMTguNDYxOCAxNi4yMTM0QzE4Ljk4MSAxNS45MjA3IDE5LjUwMDIgMTUuNzg1OCAyMC4wNDE5IDE1Ljc4NThIMjguNTk3MkMyOS4xMzkgMTUuNzg1OCAyOS42MTMxIDE1LjkyMDcgMzAuMDE5MyAxNi4yMTM0QzMwLjM1NzkgMTYuNDYwOSAzMC42Mjg5IDE2Ljc5ODUgMzAuODA5NSAxNy4xODFDMzEuMTI1NSAxNy40NTExIDMxLjM5NjMgMTcuNzg4NiAzMS41NTQ0IDE4LjE5MzdaTTI3LjQ5MTIgMjMuMzY5N0wyOC4wNTU1IDIwLjI2NDFDMjguMDMyOSAyMC4yNDE1IDI4LjAzMjkgMjAuMjE5MSAyOC4wMTA0IDIwLjIxOTFIMjcuODk3NUwyNy4zNzgzIDIzLjA5OTZDMjcuMzc4MyAyMy4xNDQ3IDI3LjMxMDYgMjMuMTg5NiAyNy4yNjU1IDIzLjE4OTZIMTkuMzQyMUwxOS4yOTcgMjMuMzY5N0gyNy40OTEyWk0xOS4wOTM5IDIzLjE4OTZIMTguODQ1NUwxOC44MjI5IDIzLjM2OTdIMTkuMDcxM0wxOS4wOTM5IDIzLjE4OTZaTTE5LjUwMDIgMjAuMjY0MUwxOC45MTMzIDIyLjk2NDZIMTkuMTYxNUwxOS43NDg0IDIwLjIxOTFIMTkuNTQ1M0MxOS41NDUzIDIwLjIxOTEgMTkuNTIyNyAyMC4yNDE1IDE5LjUwMDIgMjAuMjY0MVpNMjcuNjcxOCAyMC4yMTkxSDE5Ljk3NDJMMTkuMzg3MiAyMi45NjQ2SDI3LjE3NTFMMjcuNjcxOCAyMC4yMTkxWk0xMy43ODkgMzEuMDQzNkgxMy45NDdMMTUuMDk4NCAyNi4xMTUySDE1LjE2NkwxNi43MjM2IDE5LjI5NjRDMTYuODU5MSAxOC43NTYzIDE3LjEwNzMgMTguMjM4OCAxNy40Njg1IDE3Ljc2NjFDMTcuODI5NiAxNy4zMTYxIDE4LjI4MTIgMTYuOTMzNCAxOC43Nzc4IDE2LjY2MzNDMTkuMDI2MSAxNi41Mjg0IDE5LjI3NDUgMTYuNDM4NCAxOS41MjI3IDE2LjM0ODNMMTkuNTAwMiAxNi4zMDM0QzE5Ljc0ODQgMTYuMjEzNCAyMC4wMTkzIDE2LjE5MDggMjAuMjkwMyAxNi4xOTA4SDI4Ljg2ODJDMjkuMjI5NCAxNi4xOTA4IDI5LjU5MDUgMTYuMjU4MyAyOS44ODQgMTYuMzkzNEMyOS41MjI5IDE2LjEyMzMgMjkuMDkzOSAxNi4wMTA4IDI4LjU5NzIgMTYuMDEwOEgyMC4wNDE5QzE5LjU0NTMgMTYuMDEwOCAxOS4wNDg2IDE2LjE0NTkgMTguNTc0NyAxNi4zOTM0QzE4LjA3OCAxNi42NjMzIDE3LjY0OTEgMTcuMDIzNSAxNy4zMTA0IDE3LjQ5NkMxNi45NDkzIDE3Ljk0NjIgMTYuNzAxIDE4LjQ0MTIgMTYuNTg4MSAxOC45NTg5TDEzLjc4OSAzMS4wNDM2Wk0xNy4yMjAyIDMxLjg1MzdMMTguMTkxIDI3LjM5OEgxNy45NDI2TDE3LjAxNzEgMzEuNTgzNkgxNi45NDkzTDE2LjkwNDIgMzEuODUzN0gxNy4yMjAyWk0yNS45MzM2IDMxLjA0MzZIMjYuMTE0MkwyNi43Njg5IDI3LjM5OEgyNi41ODgzTDI1LjkzMzYgMzEuMDQzNlpNMzEuNDg2NyAxOS43NDY0QzMxLjU3NjkgMTkuMjA2NCAzMS41MDkzIDE4LjcxMTMgMzEuMzI4NyAxOC4yNjEyQzMxLjI4MzYgMTguMTQ4OCAzMS4yMTU4IDE4LjAzNjIgMzEuMTQ4MSAxNy45MjM2QzMxLjI4MzYgMTguMzUxMiAzMS4zMjg3IDE4LjgwMTQgMzEuMjM4MyAxOS4yOTY0TDMwLjA4NzIgMjYuMTE1MkwzMC4xNTQ4IDI2LjEzNzdMMjkuMjUxOSAzMS42MDYySDI5LjE4NDNMMjkuMTM5IDMxLjg3NjNIMjkuNDU1TDMxLjQ4NjcgMTkuNzQ2NFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNy42ODQ3IDIuNDI3MTdIMTcuNjYxOEMxNy44NjgyIDIuODk5MTIgMTcuOTE0IDMuNDM4NDkgMTcuODIyMiA0LjAwMDMzTDE3LjU5MjkgNS4zOTM3SDE3LjA0MjNMMTYuOTczNSA1LjczMDhIMTMuOTY4NEwxNC4xMjkxIDQuODk5MjhIMTQuMDM3M0wxNC4xMDYyIDQuNTg0NjRIMTMuODUzOEMxMy44MDggNC41ODQ2NCAxMy43ODUxIDQuNTYyMTcgMTMuNzYyIDQuNTM5NjlDMTMuNzM5MSA0LjUxNzIzIDEzLjczOTEgNC40OTQ3NSAxMy43MzkxIDQuNDQ5OEg1LjkxNjg2TDUuNTQ5ODcgNi4wOTAzOUgxMy41Nzg1QzE0LjEyOTEgNi4wOTAzOSAxNC42MzM3IDYuMjQ3NzEgMTUuMDQ2NyA2LjUzOTg3QzE1LjM2NzggNi43NDIxMyAxNS41OTcxIDcuMDU2NzcgMTUuODAzNiA3LjM3MTQxQzE1Ljg0OTUgNy40Mzg4OSAxNS44NzI0IDcuNDgzODIgMTUuOTE4NCA3LjU1MTEyQzE2LjIxNjYgNy43OTgzMiAxNi40Njg4IDguMTEyOTkgMTYuNjI5NSA4LjQ5NTE0QzE2LjgzNTkgOC45NjY5OCAxNi45MDQ4IDkuNTA2NDcgMTYuNzkwMSAxMC4wOTA3TDE2LjI4NTMgMTMuMTY5NkMxNi4xOTM1IDEzLjczMTUgMTUuOTQxMyAxNC4yNDg0IDE1LjU3NDMgMTQuNzQyOEMxNS4yMDcyIDE1LjIxNDcgMTQuNzQ4NCAxNS41OTY4IDE0LjIyMDggMTUuODg4OUMxNC4xNTIgMTUuOTExNSAxNC4wNjAyIDE1Ljk1NjQgMTMuOTkxNSAxNS45Nzg4QzEzLjg3NjcgMTYuMDY4OCAxMy43NjIgMTYuMTU4NyAxMy42MjQ1IDE2LjIyNkMxMy4wOTY4IDE2LjQ5NTcgMTIuNTY5MiAxNi42NTMxIDExLjk5NTcgMTYuNjUzMUgyLjYzNjYxQzIuMDg2MDcgMTYuNjUzMSAxLjU4MTQxIDE2LjQ5NTcgMS4xNjg1IDE2LjIyNkMwLjc1NTYxIDE1Ljk1NjQgMC40ODAzMzEgMTUuNTc0MyAwLjMxOTc1IDE1LjEyNDhDMC4xODIxMiAxNC43NjUyIDAuMTU5MTg3IDE0LjQwNTggMC4xODIxMTkgMTQuMDAxMkMwLjE4MjExOSAxMy45NTYxIDAuMTM2MjU0IDEzLjkzMzggMC4xMzYyNTQgMTMuODg4OEMtMC4wMjQzMjYxIDEzLjQxNjggLTAuMDQ3MjU4MSAxMi44Nzc1IDAuMDkwMzcyNSAxMi4zMTU2TDAuMzg4NTgzIDExLjAxMjJDMC4zODg1ODMgMTAuOTY3MyAwLjQ1NzM5OCAxMC45MjIzIDAuNTAzMjY0IDEwLjkyMjNIMy41NTQxN0MzLjU3NzExIDEwLjkyMjMgMy42MDAwNCAxMC45NDQ3IDMuNjIyOTkgMTAuOTY3M0MzLjY0NTkyIDEwLjk4OTYgMy42Njg4NyAxMS4wMzQ2IDMuNjQ1OTIgMTEuMDU3MUwzLjYwMDA0IDExLjMyNjlIMy44OTgyNUwzLjgwNjUgMTEuNzMxNEg0LjMxMTE2TDQuMjE5MzkgMTIuMjAzMkgxMi4yNzFDMTIuMjcxIDEyLjIwMzIgMTIuMjcxIDEyLjIwMzIgMTIuMjkzOSAxMi4xODA4QzEyLjI5MzkgMTIuMTU4MyAxMi4zMTY4IDEyLjE1ODMgMTIuMzE2OCAxMi4xNTgzTDEyLjU5MjEgMTAuNTYyN0gzLjk5MDAyQzMuNDM5NDggMTAuNTYyNyAyLjk1Nzc1IDEwLjQyNzggMi41Njc3OSAxMC4xMzU2QzIuMTU0ODggOS44NjU5IDEuODc5NjIgOS41MDY0NyAxLjcxOTA0IDkuMDM0NDZDMS42MDQzNCA4LjY5NzQxIDEuNTU4NDYgOC4zMzc4MSAxLjYwNDM0IDcuOTMzMjhDMS42MDQzNCA3Ljg4ODM1IDEuNTU4NDYgNy44NjU4IDEuNTU4NDYgNy44MjA4N0MxLjM5NzkgNy4zNDg4NiAxLjM3NDk3IDYuODA5NTYgMS41MTI2IDYuMjI1MjNMMi4yNDY2NSAzLjE0NjM0QzIuMzg0MjggMi41ODQ0OSAyLjYzNjYxIDIuMDY3NTggMy4wMDM2MyAxLjU3MzE2QzMuMzcwNjYgMS4xMDEyMiAzLjgyOTQ0IDAuNzE5MTUyIDQuMzU3MDQgMC40NDk0NzZDNC44ODQ2MyAwLjE1NzMxOSA1LjQxMjI0IDAgNS45NjI4MyAwSDE0LjcwMjZDMTUuMjMwMSAwIDE1LjcxMTggMC4xNTczMTkgMTYuMTI0OCAwLjQ0OTQ3NkMxNi40NDU5IDAuNjc0MjA2IDE2LjY3NTMgMC45NjYzOCAxNi44NTg4IDEuMzAzNDhDMTYuOTA0OCAxLjM0ODQzIDE2LjkyNzcgMS4zOTMzNyAxNi45NzM1IDEuNDYwOEMxNy4yNzE4IDEuNzMwNDggMTcuNTI0IDIuMDIyNjQgMTcuNjg0NyAyLjQyNzE3Wk0zLjY0NTkyIDEyLjU4NTRWMTIuNjA3OEgzLjg5ODI1TDMuOTIxMiAxMi40MjhIMy42Njg4N0wzLjY0NTkyIDEyLjU2MjhDMy42MjI5OSAxMi41NjI4IDMuNjIyOTkgMTIuNTg1NCAzLjY0NTkyIDEyLjU4NTRaTTQuOTk5MzMgNi41NjIzNUg1LjIwNTc4TDUuMjc0NjEgNi4zMTUxNEg0Ljk1MzQ1TDQuOTA3NTggNi40NDk5N0M0LjkwNzU4IDYuNDk0OTIgNC45MDc1OCA2LjUxNzQgNC45MzA1MSA2LjUzOTg3QzQuOTUzNDUgNi41NjIzNSA0Ljk3NjQgNi41NjIzNSA0Ljk5OTMzIDYuNTYyMzVaTTEuNzQxOTcgNi4yNzAxN0MxLjY1MDIzIDYuNjc0NyAxLjY1MDIzIDcuMDU2NzcgMS43MTkwNCA3LjM5Mzc5TDEuNzQxOTcgNy4yMzY1NUMxLjc2NDkyIDcuMDExODEgMS43ODc4NiA2LjgwOTU2IDEuODMzNzQgNi41ODQ4MUwyLjU0NDg0IDMuNTI4MzlDMi42ODI0OSAyLjk2NjU0IDIuOTM0ODIgMi40NDk2NSAzLjMwMTg1IDEuOTc3NjlDMy4zNDc3MSAxLjkzMjc0IDMuMzcwNjYgMS44ODc4IDMuMzkzNTkgMS44NDI4NUwzLjQ2MjQxIDEuOTEwMjhDMy44MDY1IDEuNDgzMjcgNC4xOTY0NiAxLjE0NjE2IDQuNjc4MTkgMC44OTg5NTNDNS4xODI4NCAwLjYyOTI2IDUuNjg3NSAwLjQ5NDQyMyA2LjIxNTA2IDAuNDk0NDIzSDE0Ljk3NzlDMTUuMTg0MyAwLjQ5NDQyMyAxNS4zOTA3IDAuNTE2OTA0IDE1LjU5NzEgMC41NjE4NUwxNS42MiAwLjQ5NDQyM0MxNS43MzQ5IDAuNTE2OTA0IDE1Ljg0OTUgMC41NjE4NSAxNS45NjQyIDAuNjA2Nzk2QzE1LjU5NzEgMC4zNTk1ODUgMTUuMTg0MyAwLjI0NzIxMSAxNC43MDI2IDAuMjQ3MjExSDUuOTYyODNDNS40NTgxMiAwLjI0NzIxMSA0Ljk1MzQ1IDAuMzU5NTg0IDQuNDcxNzQgMC42MjkyNkMzLjk2NzA3IDAuODk4OTUzIDMuNTU0MTcgMS4yNTg1NCAzLjE4NzE1IDEuNzA4MDFDMi44NDMwNSAyLjE3OTk1IDIuNTkwNzIgMi42NzQzOCAyLjQ3NjAzIDMuMTkxMjhMMS43NDE5NyA2LjI3MDE3Wk0xMi4yNzEgMTIuNDI4SDQuMTczNTNMNC4xMjc2NSAxMi42MDc4SDcuNzI5MVYxMi42NzUySDEyLjU2OTJDMTIuNTkyMSAxMi42NzUyIDEyLjYxNSAxMi42NTI3IDEyLjY2MSAxMi42MzAzQzEyLjY4MzkgMTIuNjA3OCAxMi43MDY4IDEyLjU2MjggMTIuNzA2OCAxMi41NDAzTDEyLjg0NDUgMTEuODIxMkwxMy4wNTEgMTAuNjc1QzEzLjA3MzkgMTAuNjMgMTMuMDUxIDEwLjYwNzcgMTMuMDI4MSAxMC41ODUxQzEzLjAwNSAxMC41NjI3IDEyLjk4MjEgMTAuNTYyNyAxMi45NTkyIDEwLjU2MjdIMTIuODQ0NUwxMi41MjMzIDEyLjIwMzJDMTIuNTIzMyAxMi4yNDgyIDEyLjUwMDQgMTIuMzE1NiAxMi40MzE3IDEyLjM2MDZDMTIuMzg1NyAxMi40MDU1IDEyLjMxNjggMTIuNDI4IDEyLjI3MSAxMi40MjhaTTQuMDM1OSAxMS45NTZIMy43NjA2MkwzLjcxNDc0IDEyLjIwMzJIMy45NjcwN0w0LjAzNTkgMTEuOTU2Wk0wLjI5NjgxNyAxMi4zNjA2QzAuMjA1MDY5IDEyLjc2NTEgMC4yMDUwNyAxMy4xNjk2IDAuMjczODg2IDEzLjUyOTJMMC4zMTk3NSAxMy4zMDQ0QzAuMzE5NzUgMTMuMTAyMSAwLjM0MjcgMTIuODk5OSAwLjQxMTUxNiAxMi42NzUyTDAuNzA5NzI3IDExLjMyNjlIMy4zNzA2NkwzLjM5MzU5IDExLjE0N0gwLjU5NTAyOUwwLjI5NjgxNyAxMi4zNjA2Wk0xNi41ODM1IDEwLjA0NThDMTYuNjc1MyA5LjUwNjQ3IDE2LjYwNjYgOS4wMTE5MSAxNi40MjMgOC41ODVDMTYuNDAwMSA4LjU0MDA3IDE2LjM3NzEgOC40OTUxNCAxNi4zNTQyIDguNDUwMjFDMTYuNDAwMSA4LjY1MjQ4IDE2LjQyMyA4Ljg1NDc0IDE2LjQyMyA5LjA3OTM5QzE2LjQyMyA5LjI1OTI3IDE2LjQyMyA5LjQzODk5IDE2LjM3NzEgOS42MTg3TDE1Ljg3MjQgMTIuNjk3NkMxNS44MjY2IDEyLjkyMjQgMTUuNzU3OCAxMy4xMjQ3IDE1LjY4ODkgMTMuMzI3TDE1LjY0MzEgMTMuNTk2N0MxNS41NTE0IDE0LjA2ODUgMTUuMzQ0OSAxNC41MTggMTUuMDQ2NyAxNC45NDUxQzE1LjE2MTQgMTQuODMyNyAxNS4yOTkgMTQuNzIwMyAxNS4zOTA3IDE0LjYwOEMxNS43MzQ5IDE0LjE1ODQgMTUuOTY0MiAxMy42NDE2IDE2LjA1NiAxMy4xMjQ3TDE2LjU4MzUgMTAuMDQ1OFpNMTMuNTc4NSA2LjMxNTE0SDUuNTAzOTlMNS40NTgxMiA2LjU2MjM1SDEwLjA5MTdWNi40OTQ5MkgxMy44NTM4QzE0LjI0MzcgNi40OTQ5MiAxNC41ODc5IDYuNTYyMzUgMTQuOTA5IDYuNzE5NjVDMTQuNTE5IDYuNDQ5OTcgMTQuMDgzMyA2LjMxNTE0IDEzLjU3ODUgNi4zMTUxNFpNNS4zMjA0NyA2LjA5MDM5TDUuNjg3NSA0LjQ0OThINS40NTgxMkM1LjQzNTE3IDQuNDQ5OCA1LjQxMjI0IDQuNDcyMjggNS4zODkyOSA0LjQ5NDc1QzUuMzQzNDIgNC41MTcyMyA1LjM0MzQyIDQuNTYyMTggNS4zMjA0NyA0LjU4NDY0TDQuOTk5MzMgNi4wOTAzOUg1LjMyMDQ3Wk0xNy41OTI5IDMuOTc3ODZDMTcuNjg0NyAzLjQzODQ5IDE3LjYzODcgMi45NDQwNyAxNy40NTUyIDIuNDk0NTlDMTcuNDU1MiAyLjQ3MjExIDE3LjQwOTQgMi40NDk2NSAxNy40MDk0IDIuNDA0NjhDMTcuNDc4MyAyLjc2NDI3IDE3LjUwMTEgMy4xNDYzNCAxNy40MzIzIDMuNTUwODVMMTcuMjAzIDQuODk5MjhIMTcuMTM0MUwxNy4wODgzIDUuMTY4OTdIMTcuNDA5NEwxNy41OTI5IDMuOTc3ODZaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzE4NzlfMTcyNzciPgo8cmVjdCB3aWR0aD0iMzEuNzQ0OSIgaGVpZ2h0PSIzNCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K' : $data['white_label_plugin_menu_icon'];
652
653 // Use 'read' capability since actual access is controlled by current_user_has_plugin_access() check above
654 $menu_capability = 'read';
655
656 // Separator just before the plugin entry, after all other plugins (Yoast etc. ~99)
657 add_action('admin_menu', function() {
658 global $menu;
659 $menu['100'] = array('', 'read', 'separator-metasync-before', '', 'wp-menu-separator');
660 }, 5);
661
662 // Main menu page at position 100.1 — bottom of the menu after all standard items
663 add_menu_page(
664 $menu_name,
665 $menu_title,
666 $menu_capability,
667 $menu_slug,
668 array($admin, 'create_admin_settings_page'),
669 $menu_icon,
670 '100.1'
671 );
672
673 // Check connection status for submenu availability
674 $general_options = Metasync::get_option('general');
675 $has_api_key = !empty($general_options['searchatlas_api_key']);
676 $has_uuid = !empty($general_options['otto_pixel_uuid']);
677 $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options);
678
679 $seo_controls = Metasync::get_option('seo_controls');
680
681 // ── Connect slug ──────────────────────────────────────────────────
682 $connect_slug = $menu_slug . '-connect';
683
684 // Dashboard
685 if (Metasync_Access_Control::user_can_access('hide_dashboard')) {
686 add_submenu_page($menu_slug, 'Dashboard', 'Dashboard', $menu_capability, $menu_slug . '-dashboard', array($admin, 'create_admin_dashboard_iframe'));
687 }
688
689 // Indexation Control
690 if (Metasync_Access_Control::user_can_access('hide_indexation_control')) {
691 add_submenu_page($menu_slug, 'Indexation Control', 'Indexation Control', $menu_capability, $menu_slug . '-seo-controls', array($admin, 'create_admin_seo_controls_page'));
692 }
693
694 // 404 Monitor
695 add_submenu_page($menu_slug, '404 Monitor', '404 Monitor', $menu_capability, $menu_slug . '-404-monitor', array($admin, 'create_admin_404_monitor_page'));
696
697 // Redirections
698 if (Metasync_Access_Control::user_can_access('hide_redirections')) {
699 add_submenu_page($menu_slug, 'Redirections', 'Redirections', $menu_capability, $menu_slug . '-redirections', array($admin, 'create_admin_redirections_page'));
700 }
701
702 // XML Sitemap
703 if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) {
704 add_submenu_page($menu_slug, 'XML Sitemap', 'XML Sitemap', $menu_capability, $menu_slug . '-xml-sitemap', array($admin, 'create_admin_xml_sitemap_page'));
705 }
706
707 // Robots.txt
708 if (Metasync_Access_Control::user_can_access('hide_robots')) {
709 add_submenu_page($menu_slug, 'Robots.txt', 'Robots.txt', $menu_capability, $menu_slug . '-robots-txt', array($admin, 'create_admin_robots_txt_page'));
710 }
711
712 // Site Verification
713 add_submenu_page($menu_slug, 'Site Verification', 'Site Verification', $menu_capability, $menu_slug . '-search-engine-verify', array($admin, 'create_admin_search_engine_verification_page'));
714
715 // Instant Indexing (conditional)
716 if ($seo_controls['enable_googleinstantindex'] ?? false) {
717 add_submenu_page($menu_slug, 'Instant Indexing', 'Instant Indexing', $menu_capability, $menu_slug . '-instant-index', array($admin, 'create_admin_google_instant_index_page'));
718 }
719
720 // Google Console (conditional)
721 if ($general_options['enable_google_console'] ?? false) {
722 add_submenu_page($menu_slug, 'Google Console', 'Google Console', $menu_capability, $menu_slug . '-google-console', array($admin, 'create_admin_google_console_page'));
723 }
724
725 // Bing Console (conditional)
726 if ($seo_controls['enable_binginstantindex'] ?? false) {
727 add_submenu_page($menu_slug, 'Bing Console', 'Bing Console', $menu_capability, $menu_slug . '-bing-console', array($admin, 'create_admin_bing_console_page'));
728 }
729
730 // Schema Markup
731 add_submenu_page($menu_slug, 'Schema Markup', 'Schema Markup', $menu_capability, $menu_slug . '-schema-markup', array($admin, 'create_admin_schema_markup_page'));
732
733 // Import SEO Data
734 if (Metasync_Access_Control::user_can_access('hide_import_seo')) {
735 add_submenu_page($menu_slug, 'Import SEO Data', 'Import SEO Data', $menu_capability, $menu_slug . '-import-external', array($admin, 'render_import_external_data_page'));
736 }
737
738 // Settings
739 if (Metasync_Access_Control::user_can_access('hide_settings')) {
740 add_submenu_page($menu_slug, 'Settings', 'Settings', $menu_capability, $menu_slug, array($admin, 'create_admin_settings_page'));
741 }
742
743 // Optimal Settings (conditional)
744 if ($general_options['enable_optimal_settings'] ?? false) {
745 add_submenu_page($menu_slug, 'Optimal Settings', 'Optimal Settings', $menu_capability, $menu_slug . '-optimal-settings', array($admin, 'create_admin_optimal_settings_page'));
746 }
747
748 // Local Business
749 add_submenu_page($menu_slug, 'Local Business', 'Local Business', $menu_capability, $menu_slug . '-local-business', array($admin, 'create_admin_local_business_page'));
750
751 // Breadcrumbs
752 add_submenu_page($menu_slug, 'Breadcrumbs', 'Breadcrumbs', $menu_capability, $menu_slug . '-breadcrumbs', array($admin, 'create_admin_breadcrumbs_page'));
753
754 // Code Snippets
755 add_submenu_page($menu_slug, 'Code Snippets', 'Code Snippets', $menu_capability, $menu_slug . '-code-snippets', array($admin, 'create_admin_code_snippets_page'));
756
757 // Code Minification
758 add_submenu_page($menu_slug, 'Code Minification', 'Code Minification', $menu_capability, $menu_slug . '-code-minification', array($admin, 'create_admin_code_minification_page'));
759
760 // Media Optimization
761 add_submenu_page($menu_slug, 'Media Optimization', 'Media Optimization', $menu_capability, $menu_slug . '-media-optimization', array($admin, 'create_admin_media_optimization_page'));
762
763 // Custom Pages
764 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
765 add_submenu_page($menu_slug, 'Custom Pages', 'Custom Pages', $menu_capability, $menu_slug . '-custom-pages', array($admin, 'create_admin_custom_pages_page'));
766 }
767
768 // Bot Statistics
769 add_submenu_page($menu_slug, 'Bot Statistics', 'Bot Statistics', $menu_capability, $menu_slug . '-bot-statistics', array($admin, 'create_admin_bot_statistics_page'));
770
771 // SEO Health dashboard
772 add_submenu_page($menu_slug, 'SEO Health', 'SEO Health', $menu_capability, $menu_slug . '-seo-health', array($admin, 'create_admin_seo_health_page'));
773
774 // Compatibility
775 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
776 add_submenu_page($menu_slug, 'Compatibility', 'Compatibility', $menu_capability, $menu_slug . '-compatibility', array($admin, 'create_admin_compatibility_page'));
777 }
778
779 // Changes Log
780 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
781 add_submenu_page($menu_slug, 'Changes Log', 'Changes Log', $menu_capability, $menu_slug . '-sync-log', array($admin, 'create_admin_sync_log_page'));
782 }
783
784 // Report Issue
785 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
786 add_submenu_page($menu_slug, 'Report Issue', 'Report Issue', $menu_capability, $menu_slug . '-report-issue', array($admin, 'create_admin_report_issue_page'));
787 }
788
789 // ── Connect CTA (shown when not authenticated) ────────────────────
790 if (!$is_fully_connected) {
791 add_submenu_page($menu_slug, 'Connect to SearchAtlas', 'Connect to SearchAtlas', $menu_capability, $connect_slug, array($admin, 'create_admin_settings_page'));
792 }
793
794 // ── Hidden pages (no sidebar entry needed) ────────────────────────
795 add_submenu_page('', 'Setup Wizard', 'Setup Wizard', $menu_capability, $menu_slug . '-setup-wizard', array($admin->setup_wizard, 'render_wizard_page'));
796
797
798 // Rename auto-generated first submenu from plugin name to "Settings" and reorder
799 add_action('admin_menu', function() use ($menu_slug) {
800 global $submenu;
801 if (!isset($submenu[$menu_slug])) {
802 return;
803 }
804 // Remove the auto-duplicate of the main menu item (same slug as parent)
805 foreach ($submenu[$menu_slug] as $key => $item) {
806 if ($item[2] === $menu_slug) {
807 unset($submenu[$menu_slug][$key]);
808 break;
809 }
810 }
811 }, 999);
812
813 }
814
815 /**
816 * Render the grouped navigation menu with Dashboard + SEO/Plugin dropdowns.
817 */
818 public function render_navigation_menu($current_page = null)
819 {
820 $page_slug = Metasync_Admin::$page_slug;
821 $available_menu_items = $this->get_available_menu_items();
822
823 $menu_icons = [
824 'general' => 'admin-settings',
825 'dashboard' => 'dashboard',
826 'compatibility' => 'admin-tools',
827 'sync_log' => 'list-view',
828 'seo_controls' => 'search',
829 'optimal_settings' => 'superhero-alt',
830 'instant_index' => 'performance',
831 'google_console' => 'chart-area',
832 'bing_console' => 'chart-bar',
833 'redirections' => 'undo',
834 'robots_txt' => 'shield',
835 'xml_sitemap' => 'networking',
836 'schema_markup' => 'tag',
837 'site_verification'=> 'yes-alt',
838 'import_seo' => 'download',
839 'custom_pages' => 'admin-page',
840 'code_minification'=> 'media-code',
841 'bot_statistics' => 'visibility',
842 'breadcrumbs' => 'menu',
843 'monitor_404' => 'warning',
844 'local_business' => 'building',
845 'code_snippets' => 'editor-code',
846 'report_issue' => 'sos',
847 'error_log' => 'warning',
848 'seo_health' => 'heart'
849
850 ];
851
852 $seo_items = [];
853 $plugin_items = [];
854
855 foreach ($available_menu_items as $key => $menu_item) {
856 $group = $menu_item['group'] ?? 'plugin';
857 if ($group === 'seo') {
858 $seo_items[$key] = $menu_item;
859 } else {
860 $plugin_items[$key] = $menu_item;
861 }
862 }
863 ?>
864 <!-- Plugin Navigation Menu with Dashboard + Grouped Dropdowns -->
865 <div class="metasync-nav-wrapper">
866 <div class="metasync-nav-tabs metasync-nav-grouped">
867 <?php
868 // Dashboard tab (standalone)
869 if (isset($seo_items['dashboard'])) {
870 $is_active = ($current_page === 'dashboard');
871 $icon = $menu_icons['dashboard'] ?? 'admin-generic';
872 $page_url = '?page=' . $page_slug . $seo_items['dashboard']['slug_suffix'];
873 ?>
874 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
875 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
876 <span class="tab-text"><?php echo esc_html($seo_items['dashboard']['title']); ?></span>
877 </a>
878 <?php
879 }
880 ?>
881
882 <!-- SEO Features Dropdown (excluding Dashboard) -->
883 <div class="metasync-nav-dropdown">
884 <?php
885 $has_active_seo = false;
886 foreach ($seo_items as $key => $menu_item) {
887 if ($key !== 'dashboard' && $current_page === $key) {
888 $has_active_seo = true;
889 break;
890 }
891 }
892 ?>
893 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_seo ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
894 <span class="tab-icon"><span class="dashicons dashicons-search"></span></span>
895 <span class="tab-text">SEO</span>
896 <span class="dropdown-arrow"></span>
897 </button>
898 <div class="metasync-nav-dropdown-menu">
899 <?php
900 foreach ($seo_items as $key => $menu_item) {
901 if ($key === 'dashboard') {
902 continue;
903 }
904
905 $is_active = ($current_page === $key);
906 $icon = $menu_icons[$key] ?? 'admin-generic';
907 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
908 ?>
909 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
910 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
911 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
912 </a>
913 <?php
914 }
915 ?>
916 </div>
917 </div>
918
919 <!-- Plugin Dropdown -->
920 <div class="metasync-nav-dropdown">
921 <?php
922 $has_active_plugin = false;
923 foreach ($plugin_items as $key => $menu_item) {
924 if ($key !== 'report_issue' && $current_page === $key) {
925 $has_active_plugin = true;
926 break;
927 }
928 }
929 ?>
930 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_plugin ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
931 <span class="tab-icon"><span class="dashicons dashicons-admin-settings"></span></span>
932 <span class="tab-text">Plugin</span>
933 <span class="dropdown-arrow"></span>
934 </button>
935 <div class="metasync-nav-dropdown-menu">
936 <?php
937 foreach ($plugin_items as $key => $menu_item) {
938 if ($key === 'report_issue') {
939 continue;
940 }
941
942 $is_active = ($current_page === $key);
943 $icon = $menu_icons[$key] ?? 'admin-generic';
944 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
945 ?>
946 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
947 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
948 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
949 </a>
950 <?php
951 }
952 ?>
953 </div>
954 </div>
955
956 <!-- Right side - Report Issue and Settings dropdown -->
957 <div class="metasync-nav-right">
958 <?php
959 if (isset($available_menu_items['report_issue'])) {
960 $is_active = ($current_page === 'report_issue');
961 $page_url = '?page=' . $page_slug . $available_menu_items['report_issue']['slug_suffix'];
962 ?>
963 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
964 <span class="tab-icon"><span class="dashicons dashicons-sos"></span></span>
965 <span class="tab-text">Report Issue</span>
966 </a>
967 <?php } ?>
968 <div class="metasync-simple-dropdown">
969 <button type="button" class="metasync-settings-btn" id="metasync-settings-btn" onclick="toggleSettingsMenuPortal(event)" aria-expanded="false">
970 <span class="tab-icon"><span class="dashicons dashicons-admin-settings"></span></span>
971 <span class="tab-text">Settings</span>
972 <span class="dropdown-arrow"></span>
973 </button>
974 </div>
975 </div>
976 </div>
977 </div>
978
979 <script>
980 // Handle navigation dropdowns using portal pattern for reliable positioning
981 (function() {
982 var dropdowns = document.querySelectorAll('.metasync-nav-dropdown');
983 var activePortal = null;
984 var activeButton = null;
985 var activeDropdown = null;
986 var scrollHandler = null;
987 var resizeHandler = null;
988
989 function positionPortalMenu(button, portalMenu) {
990 var rect = button.getBoundingClientRect();
991 var menuRect = portalMenu.getBoundingClientRect();
992 var viewportWidth = window.innerWidth;
993 var viewportHeight = window.innerHeight;
994
995 var left = rect.left;
996 if (left + menuRect.width > viewportWidth - 10) {
997 left = Math.max(10, viewportWidth - menuRect.width - 10);
998 }
999
1000 var top = rect.bottom + 8;
1001 if (top + menuRect.height > viewportHeight - 10 && rect.top > menuRect.height + 10) {
1002 top = rect.top - menuRect.height - 8;
1003 }
1004
1005 portalMenu.style.position = 'fixed';
1006 portalMenu.style.top = top + 'px';
1007 portalMenu.style.left = left + 'px';
1008 portalMenu.style.zIndex = '999999999';
1009 }
1010
1011 function closeActivePortal() {
1012 if (activePortal && activePortal.parentNode) {
1013 activePortal.parentNode.removeChild(activePortal);
1014 }
1015 if (activeButton) {
1016 activeButton.setAttribute('aria-expanded', 'false');
1017 }
1018 if (activeDropdown) {
1019 activeDropdown.classList.remove('active');
1020 }
1021 if (scrollHandler) {
1022 window.removeEventListener('scroll', scrollHandler, true);
1023 scrollHandler = null;
1024 }
1025 if (resizeHandler) {
1026 window.removeEventListener('resize', resizeHandler);
1027 resizeHandler = null;
1028 }
1029 activePortal = null;
1030 activeButton = null;
1031 activeDropdown = null;
1032 }
1033
1034 function openAsPortal(dropdown, button, menu) {
1035 closeActivePortal();
1036
1037 var portalMenu = menu.cloneNode(true);
1038 portalMenu.id = 'metasync-nav-portal-menu';
1039 portalMenu.style.opacity = '1';
1040 portalMenu.style.visibility = 'visible';
1041 portalMenu.style.transform = 'none';
1042
1043 document.body.appendChild(portalMenu);
1044
1045 positionPortalMenu(button, portalMenu);
1046
1047 activePortal = portalMenu;
1048 activeButton = button;
1049 activeDropdown = dropdown;
1050 dropdown.classList.add('active');
1051 button.setAttribute('aria-expanded', 'true');
1052
1053 scrollHandler = function() {
1054 if (activePortal && activeButton) {
1055 positionPortalMenu(activeButton, activePortal);
1056 }
1057 };
1058 resizeHandler = scrollHandler;
1059
1060 window.addEventListener('scroll', scrollHandler, true);
1061 window.addEventListener('resize', resizeHandler);
1062
1063 portalMenu.addEventListener('click', function(e) {
1064 var link = e.target.closest('a');
1065 if (link) {
1066 setTimeout(closeActivePortal, 0);
1067 }
1068 });
1069 }
1070
1071 dropdowns.forEach(function(dropdown) {
1072 var button = dropdown.querySelector('.metasync-nav-dropdown-btn');
1073 var menu = dropdown.querySelector('.metasync-nav-dropdown-menu');
1074
1075 if (!button || !menu) return;
1076
1077 button.addEventListener('click', function(e) {
1078 e.preventDefault();
1079 e.stopPropagation();
1080
1081 var isExpanded = button.getAttribute('aria-expanded') === 'true';
1082
1083 if (isExpanded) {
1084 closeActivePortal();
1085 } else {
1086 openAsPortal(dropdown, button, menu);
1087 }
1088 });
1089 });
1090
1091 document.addEventListener('click', function(e) {
1092 if (activePortal && activeButton) {
1093 if (!activePortal.contains(e.target) && !activeButton.contains(e.target)) {
1094 closeActivePortal();
1095 }
1096 }
1097 });
1098
1099 document.addEventListener('keydown', function(e) {
1100 if (e.key === 'Escape' && activePortal) {
1101 closeActivePortal();
1102 if (activeButton) {
1103 activeButton.focus();
1104 }
1105 }
1106 });
1107 })();
1108
1109 // Portal-style dropdown that bypasses stacking contexts
1110 function toggleSettingsMenuPortal(event) {
1111 event.preventDefault();
1112 event.stopPropagation();
1113
1114 var button = event.currentTarget;
1115 var existingMenu = document.getElementById('metasync-portal-menu');
1116
1117 if (existingMenu) {
1118 existingMenu.remove();
1119 button.classList.remove('active');
1120 button.setAttribute('aria-expanded', 'false');
1121 return;
1122 }
1123
1124 var menu = document.createElement('div');
1125 menu.id = 'metasync-portal-menu';
1126 menu.className = 'metasync-portal-menu';
1127
1128 var currentUrl = window.location.href;
1129 var isGeneralActive = currentUrl.indexOf('tab=general') > -1 || currentUrl.indexOf('tab=') === -1;
1130 var isAdvancedActive = currentUrl.indexOf('tab=advanced') > -1;
1131 var isWhitelabelActive = currentUrl.indexOf('tab=whitelabel') > -1;
1132
1133 menu.textContent = '';
1134
1135 var hideAdvanced = <?php
1136 echo !Metasync_Access_Control::user_can_access('hide_advanced') ? 'true' : 'false';
1137 ?>;
1138 var showGeneral = <?php
1139 echo Metasync_Access_Control::user_can_access('hide_settings') ? 'true' : 'false';
1140 ?>;
1141
1142 if (showGeneral) {
1143 const generalLink = document.createElement('a');
1144 generalLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=general';
1145 generalLink.className = 'metasync-portal-item' + (isGeneralActive ? ' active' : '');
1146 generalLink.textContent = 'General';
1147 menu.appendChild(generalLink);
1148 }
1149
1150 const whitelabelLink = document.createElement('a');
1151 whitelabelLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=whitelabel';
1152 whitelabelLink.className = 'metasync-portal-item' + (isWhitelabelActive ? ' active' : '');
1153 whitelabelLink.textContent = 'White label';
1154 menu.appendChild(whitelabelLink);
1155
1156 if (!hideAdvanced) {
1157 const advancedLink = document.createElement('a');
1158 advancedLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=advanced';
1159 advancedLink.className = 'metasync-portal-item' + (isAdvancedActive ? ' active' : '');
1160 advancedLink.textContent = 'Advanced';
1161 menu.appendChild(advancedLink);
1162 }
1163
1164
1165 var rect = button.getBoundingClientRect();
1166 menu.style.position = 'fixed';
1167 menu.style.top = (rect.bottom + 8) + 'px';
1168 menu.style.right = (window.innerWidth - rect.right) + 'px';
1169 menu.style.zIndex = '999999999';
1170
1171 document.body.appendChild(menu);
1172
1173 button.classList.add('active');
1174 button.setAttribute('aria-expanded', 'true');
1175 }
1176
1177 document.addEventListener('click', function(event) {
1178 var button = document.getElementById('metasync-settings-btn');
1179 var menu = document.getElementById('metasync-portal-menu');
1180
1181 if (menu && button && !button.contains(event.target) && !menu.contains(event.target)) {
1182 menu.remove();
1183 button.classList.remove('active');
1184 button.setAttribute('aria-expanded', 'false');
1185 }
1186 });
1187 </script>
1188 <?php
1189 }
1190
1191 /**
1192 * Render the plugin header with logo, theme toggle, and connection badge.
1193 */
1194 public function render_plugin_header($page_title = null)
1195 {
1196 $general_settings = Metasync::get_option('general');
1197
1198 $effective_plugin_name = Metasync::get_effective_plugin_name();
1199 $display_title = $page_title ?: $effective_plugin_name;
1200 $logo = $this->resolve_logo_data();
1201
1202 $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
1203 $otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : '';
1204
1205 $is_integrated = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1206
1207 $current_theme = get_option('metasync_theme', 'dark');
1208 ?>
1209
1210 <!-- Plugin Header with Logo -->
1211 <div class="metasync-header" data-current-theme="<?php echo esc_attr($current_theme); ?>">
1212 <div class="metasync-header-left">
1213 <?php if ($logo['show_logo'] && $logo['use_dual']): ?>
1214 <div class="metasync-logo-container">
1215 <img src="<?php echo esc_url($logo['light_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-light" />
1216 <img src="<?php echo esc_url($logo['dark_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-dark" />
1217 </div>
1218 <?php elseif ($logo['show_logo'] && !empty($logo['url'])): ?>
1219 <div class="metasync-logo-container">
1220 <img src="<?php echo esc_url($logo['url']); ?>" alt="Logo" class="metasync-logo<?php echo !empty($logo['is_default']) ? ' metasync-logo-default' : ''; ?>" />
1221 </div>
1222 <?php endif; ?>
1223 </div>
1224
1225 <div class="metasync-header-right">
1226 <!-- Theme Toggle -->
1227 <div class="metasync-theme-toggle" role="group" aria-label="Theme Selector">
1228 <button class="metasync-theme-option <?php echo ($current_theme === 'light') ? 'active' : ''; ?>" data-theme="light" aria-label="Light Theme" type="button">
1229 <span class="metasync-theme-icon">&#9728;</span>
1230 <span class="theme-label">Light</span>
1231 </button>
1232 <button class="metasync-theme-option <?php echo ($current_theme === 'dark') ? 'active' : ''; ?>" data-theme="dark" aria-label="Dark Theme" type="button">
1233 <span class="metasync-theme-icon">&#9790;</span>
1234 <span class="theme-label">Dark</span>
1235 </button>
1236 </div>
1237
1238 <!-- Integration Status -->
1239 <?php
1240 if ($is_integrated && !empty($otto_pixel_uuid)) {
1241 $status_class_header = 'integrated';
1242 $status_title_header = 'Synced - Heartbeat API connectivity verified';
1243 $status_text_header = 'Synced';
1244 } elseif ($is_integrated && empty($otto_pixel_uuid)) {
1245 $status_class_header = 'warning';
1246 $status_title_header = 'Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1247 $status_text_header = 'Warning';
1248 } else {
1249 $status_class_header = 'not-integrated';
1250 $status_title_header = 'Not Synced - Heartbeat API not responding or unreachable';
1251 $status_text_header = 'Not Synced';
1252 }
1253 ?>
1254 <div class="metasync-integration-status <?php echo $status_class_header; ?>"
1255 title="<?php echo esc_attr($status_title_header); ?>">
1256 <span class="status-indicator"></span>
1257 <span class="status-text"><?php echo esc_html($status_text_header); ?></span>
1258 </div>
1259 </div>
1260 </div>
1261
1262 <!-- Page Title Below Header -->
1263 <div class="metasync-page-title">
1264 <h1><?php echo esc_html($display_title); ?></h1>
1265 </div>
1266
1267 <?php
1268 }
1269
1270 // ------------------------------------------------------------------
1271 // Admin bar status indicator
1272 // ------------------------------------------------------------------
1273
1274 /**
1275 * Output inline CSS (and optional JS) for the admin-bar status node.
1276 */
1277 public function metasync_admin_bar_style()
1278 {
1279 if (!is_admin_bar_showing()) {
1280 return;
1281 }
1282
1283 # For backward compatibility, constant takes precedence.
1284 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1285 return;
1286 }
1287
1288
1289 # Check if admin bar status is enabled via setting
1290 $general_settings = Metasync::get_option('general');
1291 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1292 if (!$show_admin_bar) {
1293 return;
1294 }
1295 ?>
1296 <style type="text/css">
1297 #wp-admin-bar-searchatlas-status .ab-item {
1298 font-weight: 500 !important;
1299 transition: all 0.2s ease !important;
1300 }
1301
1302 #wp-admin-bar-searchatlas-status:hover .ab-item {
1303 background-color: rgba(255, 255, 255, 0.1) !important;
1304 }
1305
1306 #wp-admin-bar-searchatlas-status.searchatlas-synced .ab-item {
1307 color: #46b450 !important; /* WordPress green for text */
1308 }
1309
1310 #wp-admin-bar-searchatlas-status.searchatlas-not-synced .ab-item {
1311 color: #dc3232 !important; /* WordPress red for text */
1312 }
1313
1314 #wp-admin-bar-searchatlas-status.searchatlas-warning .ab-item {
1315 color: #ffb900 !important; /* WordPress yellow for warning */
1316 }
1317
1318 /* Ensure emojis maintain their natural colors */
1319 #wp-admin-bar-searchatlas-status .ab-item {
1320 filter: none !important;
1321 }
1322
1323 /* Make status visible but subtle */
1324 #wp-admin-bar-searchatlas-status .ab-item {
1325 opacity: 0.9;
1326 }
1327
1328 #wp-admin-bar-searchatlas-status:hover .ab-item {
1329 opacity: 1;
1330 }
1331 </style>
1332
1333 <?php
1334 $page_slug = Metasync_Admin::$page_slug;
1335 $is_plugin_page = (
1336 (isset($_GET['page']) && strpos($_GET['page'], $page_slug) !== false) ||
1337 (isset($_GET['page']) && strpos($_GET['page'], 'searchatlas') !== false)
1338 );
1339 if ($is_plugin_page):
1340 ?>
1341 <script type="text/javascript">
1342 // Pass PHP variables to JavaScript
1343 window.MetasyncConfig = {
1344 pluginName: '<?php echo esc_js(Metasync::get_effective_plugin_name()); ?>',
1345 ottoName: '<?php echo esc_js(Metasync::get_whitelabel_otto_name()); ?>'
1346 };
1347
1348 jQuery(document).ready(function($) {
1349
1350 // Function to sync admin bar status
1351 function syncAdminBarStatus() {
1352 var pluginPageStatus = $('.metasync-integration-status .status-text').text();
1353 var adminBarItem = $('#wp-admin-bar-searchatlas-status .ab-item');
1354 var adminBarContainer = $('#wp-admin-bar-searchatlas-status');
1355 var pluginName = window.MetasyncConfig.pluginName;
1356
1357 if (pluginPageStatus && adminBarItem.length) {
1358 var allClasses = 'searchatlas-synced searchatlas-not-synced searchatlas-warning';
1359
1360 // Helper to update emoji in admin bar
1361 function updateAdminBarEmoji(targetEmoji, targetSvgCode) {
1362 var emojiImg = adminBarItem.find('img.emoji');
1363 if (emojiImg.length > 0) {
1364 emojiImg.attr('alt', targetEmoji);
1365 var currentSrc = emojiImg.attr('src');
1366 var updatedSrc = currentSrc.replace(/1f7e2\.svg|1f534\.svg|1f7e1\.svg/, targetSvgCode + '.svg');
1367 emojiImg.attr('src', updatedSrc);
1368 } else {
1369 var newHtml = adminBarItem.html().replace(/🟢|🔴|🟡/, targetEmoji);
1370 if (!newHtml.includes(targetEmoji) && newHtml.includes(pluginName)) {
1371 newHtml = newHtml.replace(pluginName, pluginName + ' ' + targetEmoji);
1372 }
1373 adminBarItem.html(newHtml);
1374 }
1375 }
1376
1377 if (pluginPageStatus.includes('Synced') && !pluginPageStatus.includes('Not Synced')) {
1378 // Update admin bar to synced (GREEN)
1379 updateAdminBarEmoji('🟢', '1f7e2');
1380 adminBarContainer.removeClass(allClasses).addClass('searchatlas-synced');
1381 var syncTitle = pluginName + ' - Synced (Heartbeat API connectivity verified)';
1382 adminBarContainer.attr('title', syncTitle);
1383 adminBarItem.attr('title', syncTitle);
1384
1385 } else if (pluginPageStatus.includes('Warning')) {
1386 // Update admin bar to warning (YELLOW)
1387 updateAdminBarEmoji('🟡', '1f7e1');
1388 adminBarContainer.removeClass(allClasses).addClass('searchatlas-warning');
1389 var warnTitle = pluginName + ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1390 adminBarContainer.attr('title', warnTitle);
1391 adminBarItem.attr('title', warnTitle);
1392
1393 } else if (pluginPageStatus.includes('Not Synced')) {
1394 // Update admin bar to not synced (RED)
1395 updateAdminBarEmoji('🔴', '1f534');
1396 adminBarContainer.removeClass(allClasses).addClass('searchatlas-not-synced');
1397 var notSyncTitle = pluginName + ' - Not Synced (Heartbeat API not responding or unreachable)';
1398 adminBarContainer.attr('title', notSyncTitle);
1399 adminBarItem.attr('title', notSyncTitle);
1400 }
1401 }
1402 }
1403
1404 // Sync when tabs are switched (for General/Advanced tabs)
1405 $(document).on('click', 'a[href*="tab="]', function() {
1406 setTimeout(syncAdminBarStatus, 200);
1407 });
1408
1409 // Also check every 5 seconds to keep it in sync
1410 setInterval(syncAdminBarStatus, 5000);
1411 });
1412 </script>
1413 <?php endif; ?>
1414 <?php
1415 }
1416
1417 /**
1418 * Add Search Atlas status indicator to WordPress admin bar.
1419 */
1420 public function add_searchatlas_admin_bar_status($wp_admin_bar)
1421 {
1422 if (!Metasync::current_user_has_plugin_access()) {
1423 return;
1424 }
1425
1426 # For backward compatibility, constant takes precedence.
1427 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1428 return;
1429 }
1430
1431 # Check if admin bar status is disabled via setting
1432 $general_settings = Metasync::get_option('general');
1433 if (!is_array($general_settings)) {
1434 $general_settings = [];
1435 }
1436 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1437 if (!$show_admin_bar) {
1438 return;
1439 }
1440
1441 if (!is_admin() && !apply_filters('metasync_show_admin_bar_status_frontend', false)) {
1442 return;
1443 }
1444
1445 $node = get_transient(self::CACHE_KEY);
1446
1447 if ($node === false) {
1448 $is_synced = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1449
1450 $plugin_name = Metasync::get_effective_plugin_name();
1451
1452 if ($is_synced) {
1453 $otto_uuid = $general_settings['otto_pixel_uuid'] ?? '';
1454 if (!empty($otto_uuid)) {
1455 $status_emoji = '🟢'; // Green circle for synced
1456 $title = $plugin_name . ' - Synced (Heartbeat API connectivity verified)';
1457 $status_class = 'searchatlas-synced';
1458 } else {
1459 $status_emoji = '🟡'; // Yellow circle for warning
1460 $title = $plugin_name . ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1461 $status_class = 'searchatlas-warning';
1462 }
1463 } else {
1464 $status_emoji = '🔴'; // Red circle for not synced
1465 $title = $plugin_name . ' - Not Synced (Heartbeat API not responding or unreachable)';
1466 $status_class = 'searchatlas-not-synced';
1467 }
1468
1469 $admin_bar_title = $plugin_name . ' ' . $status_emoji;
1470
1471 $node = array(
1472 'title' => $admin_bar_title,
1473 'href' => admin_url('admin.php?page=' . Metasync_Admin::$page_slug),
1474 'meta_title' => $title,
1475 'meta_class' => $status_class,
1476 );
1477
1478 set_transient(self::CACHE_KEY, $node, apply_filters('metasync_admin_bar_status_cache_ttl', 5 * MINUTE_IN_SECONDS));
1479 }
1480
1481 $wp_admin_bar->add_node(array(
1482 'id' => 'searchatlas-status',
1483 'title' => $node['title'],
1484 'href' => $node['href'],
1485 'meta' => array(
1486 'title' => $node['meta_title'],
1487 'class' => $node['meta_class'],
1488 ),
1489 ));
1490 }
1491
1492 /**
1493 * Invalidate the admin bar status cache so the next page load recomputes status.
1494 */
1495 public static function invalidate_admin_bar_status_cache()
1496 {
1497 delete_transient(self::CACHE_KEY);
1498 }
1499
1500 // ------------------------------------------------------------------
1501 // Yoast-style 3-column layout helpers
1502 // ------------------------------------------------------------------
1503
1504 /**
1505 * Open the 3-column page layout.
1506 * Call this at the start of every admin page callback instead of
1507 * render_plugin_header() + render_navigation_menu().
1508 * Close with render_layout_close().
1509 *
1510 * @param string $page_title Human-readable page title.
1511 * @param string $current_page Key matching get_available_menu_items() (e.g. 'general').
1512 * @param string $description Optional subtitle shown below the title.
1513 */
1514 public function render_layout_open($page_title = '', $current_page = '', $description = '')
1515 {
1516 $theme = esc_attr(get_option('metasync_theme', 'dark'));
1517 $plugin_name = Metasync::get_effective_plugin_name();
1518 $general = Metasync::get_option('general') ?? [];
1519 $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general);
1520 $logo = $this->resolve_logo_data();
1521
1522 $current_theme = get_option('metasync_theme', 'dark');
1523 $api_key = $general['searchatlas_api_key'] ?? '';
1524 ?>
1525 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo $theme; ?>">
1526
1527 <?php
1528 // Inject whitelabel color palette overrides
1529 $wl_settings = Metasync::get_whitelabel_settings();
1530 $wl_palette = isset($wl_settings['color_palette']) && is_array($wl_settings['color_palette']) ? $wl_settings['color_palette'] : array();
1531 if (!empty($wl_palette)) {
1532 echo '<style id="metasync-whitelabel-palette">';
1533 foreach (array('dark', 'light') as $t) {
1534 if (!empty($wl_palette[$t]) && is_array($wl_palette[$t])) {
1535 $selector = ($t === 'dark') ? ':root, [data-theme="dark"]' : '[data-theme="light"]';
1536 $vars = '';
1537 foreach ($wl_palette[$t] as $var_name => $color) {
1538 // Skip gradient partial keys — handled below
1539 if (strpos($var_name, 'gradient-') !== false) continue;
1540 if (preg_match('/^dashboard-[a-z-]+$/', $var_name) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $color)) {
1541 $vars .= '--' . esc_attr($var_name) . ':' . esc_attr($color) . ';';
1542 }
1543 }
1544 // Build gradient overrides from paired from/to colors
1545 $gradient_pairs = array(
1546 'dashboard-gradient-primary' => array('dashboard-gradient-primary-from', 'dashboard-gradient-primary-to'),
1547 'dashboard-gradient-accent' => array('dashboard-gradient-accent-from', 'dashboard-gradient-accent-to'),
1548 );
1549 foreach ($gradient_pairs as $grad_var => $pair) {
1550 $from = isset($wl_palette[$t][$pair[0]]) ? $wl_palette[$t][$pair[0]] : '';
1551 $to = isset($wl_palette[$t][$pair[1]]) ? $wl_palette[$t][$pair[1]] : '';
1552 if ($from && $to && preg_match('/^#[0-9a-fA-F]{3,8}$/', $from) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $to)) {
1553 $vars .= '--' . esc_attr($grad_var) . ':linear-gradient(135deg,' . esc_attr($from) . ' 0%,' . esc_attr($to) . ' 100%);';
1554 }
1555 }
1556 if ($vars) {
1557 echo $selector . '{' . $vars . '}';
1558 }
1559 }
1560 }
1561 echo '</style>';
1562 }
1563 ?>
1564
1565 <!-- Compact top header -->
1566 <div class="metasync-header-compact">
1567 <div style="display:flex;align-items:center;gap:10px;">
1568 <?php if ($logo['show_logo'] && $logo['use_dual']): ?>
1569 <img src="<?php echo esc_url($logo['light_url']); ?>" alt="<?php echo esc_attr($plugin_name); ?>" class="metasync-logo metasync-logo-light" style="height:28px;width:auto;">
1570 <img src="<?php echo esc_url($logo['dark_url']); ?>" alt="<?php echo esc_attr($plugin_name); ?>" class="metasync-logo metasync-logo-dark" style="height:28px;width:auto;">
1571 <?php elseif ($logo['show_logo'] && $logo['url']): ?>
1572 <img src="<?php echo esc_url($logo['url']); ?>" alt="<?php echo esc_attr($plugin_name); ?>" class="metasync-logo<?php echo !empty($logo['is_default']) ? ' metasync-logo-default' : ''; ?>" style="height:28px;width:auto;">
1573 <?php else: ?>
1574 <strong style="font-size:15px;color:var(--dashboard-text-primary);"><?php echo esc_html($plugin_name); ?></strong>
1575 <?php endif; ?>
1576 </div>
1577 <div class="metasync-header-compact-right">
1578 <div class="metasync-status <?php echo !empty($api_key) ? 'connected' : 'disconnected'; ?>">
1579 <span class="status-dot"></span>
1580 <span class="status-text"><?php echo !empty($api_key) ? 'Connected' : 'Not Connected'; ?></span>
1581 </div>
1582 <button type="button" class="metasync-theme-toggle" onclick="toggleMetasyncTheme()" title="Toggle theme">
1583 <span class="theme-icon-light">&#9728;</span>
1584 <span class="theme-icon-dark">&#9790;</span>
1585 </button>
1586 </div>
1587 </div>
1588
1589 <!-- 3-column layout -->
1590 <div class="metasync-layout">
1591
1592 <!-- Left sidenav -->
1593 <aside class="metasync-layout-nav">
1594 <?php $this->render_sidenav($current_page); ?>
1595 </aside>
1596
1597 <!-- Main content -->
1598 <main class="metasync-layout-main">
1599 <?php if ($page_title || $description): ?>
1600 <div class="metasync-page-header">
1601 <?php if ($page_title): ?>
1602 <h1><?php echo esc_html($page_title); ?></h1>
1603 <?php endif; ?>
1604 <?php if ($description): ?>
1605 <p><?php echo esc_html($description); ?></p>
1606 <?php endif; ?>
1607 </div>
1608 <?php endif; ?>
1609 <?php
1610 // Note: render_layout_close() closes </main>, renders promo sidebar, closes </div>.metasync-layout and </div>.wrap
1611 }
1612
1613 /**
1614 * Close the 3-column layout opened by render_layout_open().
1615 *
1616 * @param bool $show_promo Whether to render the right promo sidebar. Default true.
1617 * Pass false for full-width pages (e.g. dashboard iframe).
1618 */
1619 public function render_layout_close($show_promo = true)
1620 {
1621 ?>
1622 </main><!-- /.metasync-layout-main -->
1623
1624 <?php if ($show_promo):
1625 $general = Metasync::get_option('general') ?? [];
1626 $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general);
1627 ?>
1628 <!-- Right promo sidebar -->
1629 <aside class="metasync-layout-promo">
1630 <?php $this->render_promo_sidebar($is_connected); ?>
1631 </aside>
1632 <?php endif; ?>
1633
1634 </div><!-- /.metasync-layout -->
1635 </div><!-- /.metasync-dashboard-wrap -->
1636 <?php
1637 }
1638
1639 /**
1640 * Render the left sticky sidenav with grouped items.
1641 *
1642 * @param string $current_page Active page key.
1643 */
1644 public function render_sidenav($current_page = '')
1645 {
1646 $page_slug = Metasync_Admin::$page_slug;
1647 $menu_items = $this->get_available_menu_items();
1648
1649 // Dashicons names (without 'dashicons-' prefix) — monochrome, color set by CSS
1650 $icons = [
1651 'dashboard' => 'dashboard',
1652 'seo_controls' => 'search',
1653 'monitor_404' => 'warning',
1654 'redirections' => 'undo',
1655 'xml_sitemap' => 'networking',
1656 'robots_txt' => 'shield',
1657 'site_verification'=> 'yes-alt',
1658 'instant_index' => 'performance',
1659 'google_console' => 'chart-area',
1660 'bing_console' => 'chart-bar',
1661 'import_seo' => 'download',
1662 'general' => 'admin-settings',
1663 'schema_markup' => 'tag',
1664 'local_business' => 'building',
1665 'breadcrumbs' => 'menu',
1666 'code_snippets' => 'editor-code',
1667 'code_minification'=> 'media-code',
1668 'custom_pages' => 'admin-page',
1669 'optimal_settings' => 'superhero-alt',
1670 'compatibility' => 'admin-tools',
1671 'sync_log' => 'list-view',
1672 'bot_statistics' => 'visibility',
1673 'report_issue' => 'sos',
1674 'media_optimization'=> 'images-alt2',
1675 'seo_health' => 'heart'
1676 ];
1677
1678 $seo_items = [];
1679 $plugin_items = [];
1680 foreach ($menu_items as $key => $item) {
1681 if (($item['group'] ?? 'plugin') === 'seo') {
1682 $seo_items[$key] = $item;
1683 } else {
1684 $plugin_items[$key] = $item;
1685 }
1686 }
1687 ?>
1688 <nav class="metasync-sidenav">
1689
1690 <!-- SEO Features group -->
1691 <div class="metasync-sidenav-group">
1692 <div class="metasync-sidenav-group-title">SEO Features</div>
1693 <ul>
1694 <?php foreach ($seo_items as $key => $item):
1695 $is_active = ($current_page === $key);
1696 $icon = $icons[$key] ?? 'admin-generic';
1697 $url = esc_url(admin_url('admin.php?page=' . $page_slug . $item['slug_suffix']));
1698 ?>
1699 <li class="<?php echo $is_active ? 'metasync-sidenav-active' : ''; ?>">
1700 <a href="<?php echo $url; ?>">
1701 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
1702 <?php echo esc_html($item['title']); ?>
1703 </a>
1704 </li>
1705 <?php endforeach; ?>
1706 </ul>
1707 </div>
1708
1709 <!-- Plugin group -->
1710 <div class="metasync-sidenav-group">
1711 <div class="metasync-sidenav-group-title">Plugin</div>
1712 <ul>
1713 <?php foreach ($plugin_items as $key => $item):
1714 if ($key === 'report_issue') continue;
1715 $is_active = ($current_page === $key);
1716 $icon = $icons[$key] ?? 'admin-generic';
1717 $url = esc_url(admin_url('admin.php?page=' . $page_slug . $item['slug_suffix']));
1718 ?>
1719 <li class="<?php echo $is_active ? 'metasync-sidenav-active' : ''; ?>">
1720 <a href="<?php echo $url; ?>">
1721 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
1722 <?php echo esc_html($item['title']); ?>
1723 </a>
1724 </li>
1725 <?php if ($key === 'general'): ?>
1726 <li class="<?php echo $current_page === 'advanced_settings' ? 'metasync-sidenav-active' : ''; ?>" style="padding-left: 8px;">
1727 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . '&tab=advanced')); ?>">
1728 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-admin-generic"></span></span>
1729 Advanced Settings
1730 </a>
1731 </li>
1732 <li class="<?php echo $current_page === 'whitelabel' ? 'metasync-sidenav-active' : ''; ?>" style="padding-left: 8px;">
1733 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . '&tab=whitelabel')); ?>">
1734 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-tag"></span></span>
1735 Whitelabel
1736 </a>
1737 </li>
1738 <?php endif; ?>
1739 <?php endforeach; ?>
1740 </ul>
1741 </div>
1742
1743 <!-- Report Issue at the bottom -->
1744 <?php if (isset($menu_items['report_issue'])): ?>
1745 <div class="metasync-sidenav-group">
1746 <ul>
1747 <li class="<?php echo $current_page === 'report_issue' ? 'metasync-sidenav-active' : ''; ?>">
1748 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . $menu_items['report_issue']['slug_suffix'])); ?>">
1749 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-sos"></span></span>
1750 Report Issue
1751 </a>
1752 </li>
1753 </ul>
1754 </div>
1755 <?php endif; ?>
1756
1757 </nav>
1758 <?php
1759 }
1760
1761 /**
1762 * Render the right promotional sidebar.
1763 *
1764 * @param bool $is_connected Whether the plugin is authenticated.
1765 */
1766 public function render_promo_sidebar($is_connected = false)
1767 {
1768 $plugin_name = Metasync::get_effective_plugin_name();
1769 $otto_name = Metasync::get_whitelabel_otto_name();
1770 $settings_url = esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug));
1771 $homepage = Metasync::HOMEPAGE_DOMAIN;
1772 $is_default_brand = ( $plugin_name === 'Search Atlas' );
1773 $whitelabel = Metasync::get_whitelabel_settings();
1774 $custom_links = isset($whitelabel['quick_links']) && is_array($whitelabel['quick_links'])
1775 ? array_filter($whitelabel['quick_links'], function($l) { return !empty($l['url']); })
1776 : [];
1777 ?>
1778
1779 <?php if (!$is_connected): ?>
1780 <!-- Connect CTA card -->
1781 <div class="metasync-promo-card metasync-promo-card--connect">
1782 <div class="metasync-promo-card-header">
1783 <div class="metasync-promo-card-icon">
1784 <span class="dashicons dashicons-admin-links"></span>
1785 </div>
1786 <div>
1787 <h3>Connect <?php echo esc_html($plugin_name); ?></h3>
1788 </div>
1789 </div>
1790 <p class="metasync-promo-tagline">Link your site to <?php echo esc_html($plugin_name); ?> to unlock <?php echo esc_html($otto_name); ?>, keyword data, and automated SEO.</p>
1791 <ul class="metasync-promo-benefits">
1792 <li><span class="promo-check">&#10003;</span> <?php echo esc_html($otto_name); ?> hands-free on-page SEO</li>
1793 <li><span class="promo-check">&#10003;</span> Real-time keyword tracking</li>
1794 <li><span class="promo-check">&#10003;</span> Automated schema markup</li>
1795 <li><span class="promo-check">&#10003;</span> Instant Google indexing</li>
1796 </ul>
1797 <a href="<?php echo $settings_url; ?>" class="metasync-promo-btn metasync-promo-btn--primary">
1798 Connect Now
1799 </a>
1800 </div>
1801 <?php else: ?>
1802 <!-- Connected feature highlights -->
1803 <div class="metasync-promo-card metasync-promo-card--accent">
1804 <div class="metasync-promo-card-header">
1805 <div class="metasync-promo-card-icon">
1806 <span class="dashicons dashicons-performance"></span>
1807 </div>
1808 <div>
1809 <h3><?php echo esc_html($otto_name); ?> Active</h3>
1810 </div>
1811 </div>
1812 <p class="metasync-promo-tagline">Your site is connected and <?php echo esc_html($otto_name); ?> is optimizing pages automatically.</p>
1813 <ul class="metasync-promo-benefits">
1814 <li><span class="promo-check">&#10003;</span> Schema markup auto-applied</li>
1815 <li><span class="promo-check">&#10003;</span> Meta titles &amp; descriptions optimized</li>
1816 <li><span class="promo-check">&#10003;</span> Internal linking suggestions active</li>
1817 </ul>
1818 <?php if ($is_default_brand): ?>
1819 <a href="<?php echo esc_url($homepage); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1820 View <?php echo esc_html($plugin_name); ?> Dashboard
1821 </a>
1822 <?php elseif (!empty($whitelabel['domain'])): ?>
1823 <a href="<?php echo esc_url($whitelabel['domain']); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1824 View <?php echo esc_html($plugin_name); ?> Dashboard
1825 </a>
1826 <?php endif; ?>
1827 </div>
1828 <?php endif; ?>
1829
1830 <?php
1831 // Quick Links: show default links for default brand, custom links if whitelabeled + provided, hide if whitelabeled + none
1832 $show_quick_links = $is_default_brand || !empty($custom_links);
1833 if ($show_quick_links):
1834 ?>
1835 <!-- Quick links card -->
1836 <div class="metasync-promo-card">
1837 <h3 style="margin:0 0 12px;font-size:13px;font-weight:700;color:var(--dashboard-text-primary);">Quick Links</h3>
1838 <ul class="metasync-promo-links">
1839 <?php if ($is_default_brand): ?>
1840 <li><a href="<?php echo esc_url($homepage . '/blog/'); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-rss"></span> SEO Blog</a></li>
1841 <li><a href="<?php echo esc_url($homepage . '/academy/'); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-welcome-learn-more"></span> SEO Academy</a></li>
1842 <li><a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-setup-wizard')); ?>"><span class="dashicons dashicons-admin-customizer"></span> Setup Wizard</a></li>
1843 <li><a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-report-issue')); ?>"><span class="dashicons dashicons-sos"></span> Report Issue</a></li>
1844 <?php else: ?>
1845 <?php foreach ($custom_links as $link): ?>
1846 <li><a href="<?php echo esc_url($link['url']); ?>" <?php echo !empty($link['external']) ? 'target="_blank" rel="noopener"' : ''; ?>><span class="dashicons dashicons-admin-links"></span> <?php echo esc_html($link['label'] ?: $link['url']); ?></a></li>
1847 <?php endforeach; ?>
1848 <?php endif; ?>
1849 </ul>
1850 </div>
1851 <?php endif; ?>
1852
1853 <?php
1854 }
1855 }
1856