PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.5.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.5.23
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.5.23, at includes/class-metasync-admin-navigation.php

1,356 lines 59.0 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 /**
22 * Get singleton instance.
23 *
24 * @return self
25 */
26 public static function instance()
27 {
28 if (self::$instance === null) {
29 self::$instance = new self();
30 }
31 return self::$instance;
32 }
33
34 private function __construct() {}
35
36 // ------------------------------------------------------------------
37 // Header + Nav rendering
38 // ------------------------------------------------------------------
39
40 /**
41 * Render the standard header followed by the navigation tabs.
42 */
43 public function render_standard_header_nav($page_title = null, $current_page = null)
44 {
45 $this->render_static_header($page_title);
46 $this->render_static_navigation($current_page);
47 }
48
49 /**
50 * Render the page header (logo, connection badge, theme toggle).
51 */
52 public function render_static_header($page_title = null)
53 {
54 $whitelabel_settings = Metasync::get_whitelabel_settings();
55 $whitelabel_logo = Metasync::get_whitelabel_logo();
56 $is_whitelabel = isset($whitelabel_settings['is_whitelabel']) ? $whitelabel_settings['is_whitelabel'] : false;
57 $effective_plugin_name = Metasync::get_effective_plugin_name();
58 $display_title = $page_title ?: $effective_plugin_name;
59
60 $show_logo = false;
61 $logo_url = '';
62
63 if (!empty($whitelabel_logo) && filter_var($whitelabel_logo, FILTER_VALIDATE_URL)) {
64 $show_logo = true;
65 $logo_url = esc_url($whitelabel_logo);
66 } elseif (!$is_whitelabel) {
67 $show_logo = true;
68 $logo_url = Metasync::HOMEPAGE_DOMAIN . '/wp-content/uploads/2023/12/white.svg';
69 }
70
71 $current_theme = get_option('metasync_theme', 'dark');
72 $general_settings = Metasync::get_option('general');
73 $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
74 $is_integrated = !empty($searchatlas_api_key);
75 ?>
76 <div class="metasync-header" data-current-theme="<?php echo esc_attr($current_theme); ?>">
77 <div class="metasync-header-left">
78 <?php if ($show_logo && !empty($logo_url)): ?>
79 <div class="metasync-logo-container">
80 <img src="<?php echo $logo_url; ?>" alt="Logo" class="metasync-logo" />
81 </div>
82 <?php endif; ?>
83 </div>
84 <div class="metasync-header-right">
85 <div class="metasync-status <?php echo $is_integrated ? 'connected' : 'disconnected'; ?>">
86 <span class="status-dot"></span>
87 <span class="status-text"><?php echo $is_integrated ? 'Connected' : 'Not Connected'; ?></span>
88 </div>
89 <button type="button" class="metasync-theme-toggle" onclick="toggleMetasyncTheme()" title="Toggle theme">
90 <span class="theme-icon-light">☀️</span>
91 <span class="theme-icon-dark">🌙</span>
92 </button>
93 </div>
94 </div>
95 <?php
96 }
97
98 /**
99 * Render the horizontal navigation tabs.
100 */
101 public function render_static_navigation($current_page = null)
102 {
103 $general_options = Metasync::get_option('general') ?? [];
104 $whitelabel_settings = Metasync::get_whitelabel_settings();
105 $page_slug = Metasync_Admin::$page_slug;
106
107 $menu_items = [];
108 $menu_icons = [
109 'dashboard' => '📊',
110 'seo_controls' => '🔍',
111 'optimal_settings' => '🚀',
112 'instant_index' => '🔗',
113 'google_console' => '📊',
114 'compatibility' => '🔧',
115 'sync_log' => '📋',
116 'redirections' => '↩️',
117 'robots_txt' => '🤖',
118 'xml_sitemap' => '🗺️',
119 'custom_pages' => '📝',
120 'report_issue' => '📝',
121 'general' => '⚙️'
122 ];
123
124 if (empty($whitelabel_settings['hide_dashboard'])) {
125 $menu_items['dashboard'] = ['title' => 'Dashboard', 'slug_suffix' => '-dashboard'];
126 }
127
128 if (empty($whitelabel_settings['hide_indexation_control'])) {
129 $menu_items['seo_controls'] = ['title' => 'Indexation Control', 'slug_suffix' => '-seo-controls'];
130 }
131
132 if ($general_options['enable_optimal_settings'] ?? false) {
133 $menu_items['optimal_settings'] = ['title' => 'Optimal Settings', 'slug_suffix' => '-optimal-settings'];
134 }
135
136 if ($general_options['enable_googleinstantindex'] ?? false) {
137 $menu_items['instant_index'] = ['title' => 'Instant Indexing', 'slug_suffix' => '-instant-index'];
138 }
139
140 if ($general_options['enable_google_console'] ?? false) {
141 $menu_items['google_console'] = ['title' => 'Google Console', 'slug_suffix' => '-google-console'];
142 }
143
144 if (empty($whitelabel_settings['hide_compatibility'])) {
145 $menu_items['compatibility'] = ['title' => 'Compatibility', 'slug_suffix' => '-compatibility'];
146 }
147
148 if (empty($whitelabel_settings['hide_sync_log'])) {
149 $menu_items['sync_log'] = ['title' => 'Sync Log', 'slug_suffix' => '-sync-log'];
150 }
151
152 if (empty($whitelabel_settings['hide_redirections'])) {
153 $menu_items['redirections'] = ['title' => 'Redirections', 'slug_suffix' => '-redirections'];
154 }
155
156 if (empty($whitelabel_settings['hide_robots'])) {
157 $menu_items['robots_txt'] = ['title' => 'Robots.txt', 'slug_suffix' => '-robots-txt'];
158 }
159
160 $menu_items['xml_sitemap'] = ['title' => 'XML Sitemap', 'slug_suffix' => '-xml-sitemap'];
161 ?>
162 <div class="metasync-nav-wrapper">
163 <div class="metasync-nav-tabs">
164 <div class="metasync-nav-left">
165 <?php foreach ($menu_items as $key => $menu_item):
166 $is_active = ($current_page === $key);
167 $icon = $menu_icons[$key] ?? '📄';
168 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
169 ?>
170 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
171 <span class="tab-icon"><?php echo $icon; ?></span>
172 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
173 </a>
174 <?php endforeach; ?>
175 </div>
176 <div class="metasync-nav-right">
177 <a href="?page=<?php echo $page_slug; ?>-custom-pages" class="metasync-nav-tab <?php echo $current_page === 'custom_pages' ? 'active' : ''; ?>" style="margin-right: 10px;">
178 <span class="tab-icon">📝</span>
179 <span class="tab-text">Custom Pages</span>
180 </a>
181 <a href="?page=<?php echo $page_slug; ?>-report-issue" class="metasync-nav-tab <?php echo $current_page === 'report_issue' ? 'active' : ''; ?>">
182 <span class="tab-icon">📝</span>
183 <span class="tab-text">Report Issue</span>
184 </a>
185 <div class="metasync-simple-dropdown">
186 <button type="button" class="metasync-seo-btn" id="metasync-seo-btn" onclick="toggleSeoMenuPortal(event)">
187 <span class="tab-icon">🔍</span>
188 <span class="tab-text">SEO</span>
189 <span class="dropdown-arrow"></span>
190 </button>
191 </div>
192 <div class="metasync-simple-dropdown">
193 <button type="button" class="metasync-settings-btn" id="metasync-settings-btn" onclick="toggleSettingsMenuPortal(event)" aria-expanded="false">
194 <span class="tab-icon">⚙️</span>
195 <span class="tab-text">Settings</span>
196 <span class="dropdown-arrow"></span>
197 </button>
198 </div>
199 </div>
200 </div>
201 </div>
202
203 <script>
204 function toggleSeoMenuPortal(event) {
205 event.preventDefault();
206 event.stopPropagation();
207 var button = event.currentTarget;
208 var existingMenu = document.getElementById('metasync-seo-portal-menu');
209 if (existingMenu) {
210 existingMenu.remove();
211 button.classList.remove('active');
212 return;
213 }
214 var menu = document.createElement('div');
215 menu.id = 'metasync-seo-portal-menu';
216 menu.className = 'metasync-portal-menu';
217
218 var rect = button.getBoundingClientRect();
219 menu.style.position = 'fixed';
220 menu.style.top = (rect.bottom + 8) + 'px';
221 menu.style.right = (window.innerWidth - rect.right) + 'px';
222 menu.style.zIndex = '999999999';
223 document.body.appendChild(menu);
224 button.classList.add('active');
225 }
226
227 function toggleSettingsMenuPortal(event) {
228 event.preventDefault();
229 event.stopPropagation();
230 var button = event.currentTarget;
231 var existingMenu = document.getElementById('metasync-portal-menu');
232 if (existingMenu) {
233 existingMenu.remove();
234 button.classList.remove('active');
235 return;
236 }
237 var menu = document.createElement('div');
238 menu.id = 'metasync-portal-menu';
239 menu.className = 'metasync-portal-menu';
240
241 var hideAdvanced = <?php echo !empty($whitelabel_settings['hide_advanced']) ? 'true' : 'false'; ?>;
242 var showGeneral = <?php echo Metasync_Access_Control::user_can_access('hide_settings') ? 'true' : 'false'; ?>;
243
244 if (showGeneral) {
245 var generalLink = document.createElement('a');
246 generalLink.href = '?page=<?php echo $page_slug; ?>&tab=general';
247 generalLink.className = 'metasync-portal-item';
248 generalLink.textContent = 'General';
249 menu.appendChild(generalLink);
250 }
251
252 var whitelabelLink = document.createElement('a');
253 whitelabelLink.href = '?page=<?php echo $page_slug; ?>&tab=whitelabel';
254 whitelabelLink.className = 'metasync-portal-item';
255 whitelabelLink.textContent = 'White Label';
256 menu.appendChild(whitelabelLink);
257
258 if (!hideAdvanced) {
259 var advancedLink = document.createElement('a');
260 advancedLink.href = '?page=<?php echo $page_slug; ?>&tab=advanced';
261 advancedLink.className = 'metasync-portal-item';
262 advancedLink.textContent = 'Advanced';
263 menu.appendChild(advancedLink);
264 }
265
266
267 var rect = button.getBoundingClientRect();
268 menu.style.position = 'fixed';
269 menu.style.top = (rect.bottom + 8) + 'px';
270 menu.style.right = (window.innerWidth - rect.right) + 'px';
271 menu.style.zIndex = '999999999';
272 document.body.appendChild(menu);
273 button.classList.add('active');
274 }
275 document.addEventListener('click', function(event) {
276 var seoButton = document.getElementById('metasync-seo-btn');
277 var seoMenu = document.getElementById('metasync-seo-portal-menu');
278 if (seoMenu && seoButton && !seoButton.contains(event.target) && !seoMenu.contains(event.target)) {
279 seoMenu.remove();
280 seoButton.classList.remove('active');
281 }
282
283 var button = document.getElementById('metasync-settings-btn');
284 var menu = document.getElementById('metasync-portal-menu');
285 if (menu && button && !button.contains(event.target) && !menu.contains(event.target)) {
286 menu.remove();
287 button.classList.remove('active');
288 }
289 });
290 </script>
291 <?php
292 }
293
294 // ------------------------------------------------------------------
295 // Menu registration + enhanced navigation (moved from Metasync_Admin)
296 // ------------------------------------------------------------------
297
298 /**
299 * Helper method to get available menu items based on configuration.
300 * Items are grouped into 'seo' (SEO Features) and 'plugin' (Plugin) categories.
301 */
302 public function get_available_menu_items()
303 {
304 $general_options = Metasync::get_option('general') ?? [];
305 $has_api_key = !empty($general_options['searchatlas_api_key'] ?? '');
306 $has_uuid = !empty($general_options['otto_pixel_uuid'] ?? '');
307 $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options);
308
309 $menu_items = [];
310
311 // === SEO FEATURES GROUP ===
312
313 // Dashboard (check access control)
314 if (Metasync_Access_Control::user_can_access('hide_dashboard')) {
315 $menu_items['dashboard'] = [
316 'title' => 'Dashboard',
317 'slug_suffix' => '-dashboard',
318 'callback' => 'create_admin_dashboard_iframe',
319 'internal_nav' => 'Dashboard',
320 'group' => 'seo'
321 ];
322 }
323
324 // Indexation Control (check access control)
325 if (Metasync_Access_Control::user_can_access('hide_indexation_control')) {
326 $menu_items['seo_controls'] = [
327 'title' => 'Indexation',
328 'slug_suffix' => '-seo-controls',
329 'callback' => 'create_admin_seo_controls_page',
330 'internal_nav' => 'Indexation Control',
331 'group' => 'seo'
332 ];
333 }
334
335 // Instant Indexing - setting now stored in seo_controls (moved from Settings to Indexation Control)
336 $seo_controls = Metasync::get_option('seo_controls');
337 if ($seo_controls['enable_googleinstantindex'] ?? false) {
338 $menu_items['instant_index'] = [
339 'title' => 'Instant Indexing',
340 'slug_suffix' => '-instant-index',
341 'callback' => 'create_admin_google_instant_index_page',
342 'internal_nav' => 'Instant Indexing',
343 'group' => 'seo'
344 ];
345 }
346
347 if ($general_options['enable_google_console'] ?? false) {
348 $menu_items['google_console'] = [
349 'title' => 'Google Console',
350 'slug_suffix' => '-google-console',
351 'callback' => 'create_admin_google_console_page',
352 'internal_nav' => 'Google Console',
353 'group' => 'seo'
354 ];
355 }
356
357 if ($general_options['enable_bing_console'] ?? false) {
358 $menu_items['bing_console'] = [
359 'title' => 'Bing Console',
360 'slug_suffix' => '-bing-console',
361 'callback' => 'create_admin_bing_console_page',
362 'internal_nav' => 'Bing Console',
363 'group' => 'seo'
364 ];
365 }
366
367 // Redirections page (check access control)
368 if (Metasync_Access_Control::user_can_access('hide_redirections')) {
369 $menu_items['redirections'] = [
370 'title' => 'Redirections',
371 'slug_suffix' => '-redirections',
372 'callback' => 'create_admin_redirections_page',
373 'internal_nav' => 'Redirections',
374 'group' => 'seo'
375 ];
376 }
377
378 // Robots.txt page (check access control)
379 if (Metasync_Access_Control::user_can_access('hide_robots')) {
380 $menu_items['robots_txt'] = [
381 'title' => 'Robots.txt',
382 'slug_suffix' => '-robots-txt',
383 'callback' => 'create_admin_robots_txt_page',
384 'internal_nav' => 'Robots.txt',
385 'group' => 'seo'
386 ];
387 }
388
389 // XML Sitemap page (check access control)
390 if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) {
391 $menu_items['xml_sitemap'] = [
392 'title' => 'XML Sitemap',
393 'slug_suffix' => '-xml-sitemap',
394 'callback' => 'create_admin_xml_sitemap_page',
395 'internal_nav' => 'XML Sitemap',
396 'group' => 'seo'
397 ];
398 }
399
400 // Import SEO Data page (check access control)
401 if (Metasync_Access_Control::user_can_access('hide_import_seo')) {
402 $menu_items['import_seo'] = [
403 'title' => 'Import SEO Data',
404 'slug_suffix' => '-import-external',
405 'callback' => 'render_import_external_data_page',
406 'internal_nav' => 'Import SEO Data',
407 'group' => 'seo'
408 ];
409 }
410
411 // === PLUGIN GROUP ===
412
413 // Settings (renamed from General and moved after Dashboard) (check access control)
414 if (Metasync_Access_Control::user_can_access('hide_settings')) {
415 $menu_items['general'] = [
416 'title' => 'Settings',
417 'slug_suffix' => '',
418 'callback' => 'create_admin_settings_page',
419 'internal_nav' => 'General Settings',
420 'group' => 'plugin'
421 ];
422 }
423
424 if ($general_options['enable_optimal_settings'] ?? false) {
425 $menu_items['optimal_settings'] = [
426 'title' => 'Optimal Settings',
427 'slug_suffix' => '-optimal-settings',
428 'callback' => 'create_admin_optimal_settings_page',
429 'internal_nav' => 'Optimal Settings',
430 'group' => 'plugin'
431 ];
432 }
433
434 // Compatibility page (check access control)
435 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
436 $menu_items['compatibility'] = [
437 'title' => 'Compatibility',
438 'slug_suffix' => '-compatibility',
439 'callback' => 'create_admin_compatibility_page',
440 'internal_nav' => 'Compatibility',
441 'group' => 'plugin'
442 ];
443 }
444
445 // Sync Log page (check access control)
446 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
447 $menu_items['sync_log'] = [
448 'title' => 'Sync Log',
449 'slug_suffix' => '-sync-log',
450 'callback' => 'create_admin_sync_log_page',
451 'internal_nav' => 'Sync Log',
452 'group' => 'plugin'
453 ];
454 }
455
456 // Custom HTML Pages (check access control)
457 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
458 $menu_items['custom_pages'] = [
459 'title' => 'Custom Pages',
460 'slug_suffix' => '-custom-pages',
461 'callback' => 'create_admin_custom_pages_page',
462 'internal_nav' => 'Custom HTML Pages',
463 'group' => 'plugin'
464 ];
465 }
466
467 // Bot Statistics (always available)
468 $menu_items['bot_statistics'] = [
469 'title' => 'Bot Statistics',
470 'slug_suffix' => '-bot-statistics',
471 'callback' => 'create_admin_bot_statistics_page',
472 'internal_nav' => 'Bot Statistics',
473 'group' => 'plugin'
474 ];
475
476 // Report Issue page (check access control)
477 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
478 $menu_items['report_issue'] = [
479 'title' => 'Report Issue',
480 'slug_suffix' => '-report-issue',
481 'callback' => 'create_admin_report_issue_page',
482 'internal_nav' => 'Report Issue',
483 'group' => 'plugin'
484 ];
485 }
486
487 return $menu_items;
488 }
489
490 /**
491 * Register all WordPress admin menu / submenu pages.
492 *
493 * @param Metasync_Admin $admin The admin instance whose callbacks WordPress will invoke.
494 */
495 public function add_plugin_settings_page($admin)
496 {
497 if (!Metasync::current_user_has_plugin_access()) {
498 return;
499 }
500
501 $data = Metasync::get_option('general');
502 $plugin_name = Metasync::get_effective_plugin_name();
503 $menu_name = $plugin_name;
504 $menu_title = $plugin_name;
505 $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'];
506 $menu_icon = !isset($data['white_label_plugin_menu_icon']) || $data['white_label_plugin_menu_icon'] == "" ? 'dashicons-searchatlas' : $data['white_label_plugin_menu_icon'];
507
508 // Use 'read' capability since actual access is controlled by current_user_has_plugin_access() check above
509 $menu_capability = 'read';
510
511 // Main menu page - Settings (default)
512 add_menu_page(
513 $menu_name,
514 $menu_title,
515 $menu_capability,
516 $menu_slug,
517 array($admin, 'create_admin_settings_page'),
518 $menu_icon
519 );
520
521 // Check connection status for submenu availability
522 $general_options = Metasync::get_option('general');
523 $has_api_key = !empty($general_options['searchatlas_api_key']);
524 $has_uuid = !empty($general_options['otto_pixel_uuid']);
525 $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options);
526
527 // Add Dashboard submenu (check access control)
528 if (Metasync_Access_Control::user_can_access('hide_dashboard')) {
529 add_submenu_page(
530 $menu_slug,
531 'Dashboard',
532 'Dashboard',
533 $menu_capability,
534 $menu_slug . '-dashboard',
535 array($admin, 'create_admin_dashboard_iframe')
536 );
537 }
538
539 // Add Compatibility submenu (check access control)
540 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
541 add_submenu_page(
542 $menu_slug,
543 'Compatibility',
544 'Compatibility',
545 $menu_capability,
546 $menu_slug . '-compatibility',
547 array($admin, 'create_admin_compatibility_page')
548 );
549 }
550
551 // Sync Log page (check access control)
552 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
553 add_submenu_page(
554 $menu_slug,
555 'Sync Log',
556 'Sync Log',
557 $menu_capability,
558 $menu_slug . '-sync-log',
559 array($admin, 'create_admin_sync_log_page')
560 );
561 }
562
563 // Indexation Control (check access control)
564 if (Metasync_Access_Control::user_can_access('hide_indexation_control')) {
565 add_submenu_page(
566 $menu_slug,
567 'Indexation Control',
568 'Indexation Control',
569 $menu_capability,
570 $menu_slug . '-seo-controls',
571 array($admin, 'create_admin_seo_controls_page')
572 );
573 }
574
575
576 // Rename the auto-generated first submenu item from plugin name to "Settings"
577 add_action('admin_menu', function() use ($menu_slug) {
578 global $submenu;
579 if (isset($submenu[$menu_slug])) {
580 foreach ($submenu[$menu_slug] as $key => $item) {
581 if ($item[2] === $menu_slug) {
582 if (!Metasync_Access_Control::user_can_access('hide_settings')) {
583 unset($submenu[$menu_slug][$key]);
584 } else {
585 $submenu[$menu_slug][$key][0] = 'Settings';
586 }
587 break;
588 }
589 }
590
591 // Ensure proper ordering: Dashboard first, then Settings
592 if (count($submenu[$menu_slug]) > 1) {
593 usort($submenu[$menu_slug], function($a, $b) {
594 if (strpos($a[2], '-dashboard') !== false) return -1;
595 if (strpos($b[2], '-dashboard') !== false) return 1;
596 return 0;
597 });
598 }
599 }
600 }, 999);
601
602 // Additional conditional features (commented out for now - can be enabled based on settings)
603 // if(@Metasync::get_option('general')['enable_404monitor'])
604 // add_submenu_page($menu_slug, '404 Monitor', '404 Monitor', 'manage_options', $menu_slug . '-404-monitor', array($admin, 'create_admin_404_monitor_page'));
605
606 // if(@Metasync::get_option('general')['enable_siteverification'])
607 // add_submenu_page($menu_slug, 'Site Verification', 'Site Verification', 'manage_options', $menu_slug . '-search-engine-verify', array($admin, 'create_admin_search_engine_verification_page'));
608
609 // if(@Metasync::get_option('general')['enable_localbusiness'])
610 // add_submenu_page($menu_slug, 'Local Business', 'Local Business', 'manage_options', $menu_slug . '-local-business', array($admin, 'create_admin_local_business_page'));
611
612 // if(@Metasync::get_option('general')['enable_codesnippets'])
613 // add_submenu_page($menu_slug, 'Code Snippets', 'Code Snippets', 'manage_options', $menu_slug . '-code-snippets', array($admin, 'create_admin_code_snippets_page'));
614
615 // if(@Metasync::get_option('general')['enable_globalsettings'])
616 // add_submenu_page($menu_slug, 'Global Settings', 'Global Settings', 'manage_options', $menu_slug . '-common-settings', array($admin, 'create_admin_global_settings_page'));
617
618 // if(@Metasync::get_option('general')['enable_commonmetastatus'])
619 // add_submenu_page($menu_slug, 'Common Meta Status', 'Common Meta Status', 'manage_options', $menu_slug . '-common-meta-settings', array($admin, 'create_admin_common_meta_settings_page'));
620
621 // if(@Metasync::get_option('general')['enable_socialmeta'])
622 // add_submenu_page($menu_slug, 'Social Meta', 'Social Meta', 'manage_options', $menu_slug . '-social-meta', array($admin, 'create_admin_social_meta_page'));
623
624 // Redirections (check access control)
625 if (Metasync_Access_Control::user_can_access('hide_redirections')) {
626 add_submenu_page($menu_slug, 'Redirections', 'Redirections', $menu_capability, $menu_slug . '-redirections', array($admin, 'create_admin_redirections_page'));
627 }
628
629 // XML Sitemap (check access control)
630 if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) {
631 add_submenu_page($menu_slug, 'XML Sitemap', 'XML Sitemap', $menu_capability, $menu_slug . '-xml-sitemap', array($admin, 'create_admin_xml_sitemap_page'));
632 }
633
634 // Robots.txt (check access control)
635 if (Metasync_Access_Control::user_can_access('hide_robots')) {
636 add_submenu_page($menu_slug, 'Robots.txt', 'Robots.txt', $menu_capability, $menu_slug . '-robots-txt', array($admin, 'create_admin_robots_txt_page'));
637 }
638
639 // Bot Statistics (hidden from sidebar - accessible via Plugin dropdown in grouped nav)
640 add_submenu_page(null, 'Bot Statistics', 'Bot Statistics', $menu_capability, $menu_slug . '-bot-statistics', array($admin, 'create_admin_bot_statistics_page'));
641
642 // Import SEO Data (check access control)
643 if (Metasync_Access_Control::user_can_access('hide_import_seo')) {
644 add_submenu_page($menu_slug, 'Import SEO Data', 'Import SEO Data', $menu_capability, $menu_slug . '-import-external', array($admin, 'render_import_external_data_page'));
645 }
646
647 // Custom Pages (check access control)
648 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
649 add_submenu_page($menu_slug, 'Custom Pages', 'Custom Pages', $menu_capability, $menu_slug . '-custom-pages', array($admin, 'create_admin_custom_pages_page'));
650 }
651
652 // Report Issue (check access control)
653 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
654 add_submenu_page($menu_slug, 'Report Issue', 'Report Issue', $menu_capability, $menu_slug . '-report-issue', array($admin, 'create_admin_report_issue_page'));
655 }
656
657 // Setup Wizard as a hidden page (accessible via dashboard card only)
658 add_submenu_page('', 'Setup Wizard', 'Setup Wizard', $menu_capability, $menu_slug . '-setup-wizard', array($admin->setup_wizard, 'render_wizard_page'));
659
660 // 404 Monitor as a direct page (not submenu)
661 add_submenu_page('', '404 Monitor', '404 Monitor', $menu_capability, $menu_slug . '-404-monitor', array($admin, 'create_admin_404_monitor_page'));
662
663 // Google Instant Indexing (conditional - check if enabled)
664 $seo_controls = Metasync::get_option('seo_controls');
665 if ($seo_controls['enable_googleinstantindex'] ?? false) {
666 add_submenu_page($menu_slug, 'Instant Indexing', 'Instant Indexing', $menu_capability, $menu_slug . '-instant-index', array($admin, 'create_admin_google_instant_index_page'));
667 }
668
669 // Google Console (conditional - check if enabled)
670 if ($general_options['enable_google_console'] ?? false) {
671 add_submenu_page($menu_slug, 'Google Console', 'Google Console', $menu_capability, $menu_slug . '-google-console', array($admin, 'create_admin_google_console_page'));
672 }
673
674 // Bing Console (conditional - hidden page, only accessible via direct link)
675 if ($seo_controls['enable_binginstantindex'] ?? false) {
676 add_submenu_page('', 'Bing Console', 'Bing Console', $menu_capability, $menu_slug . '-bing-console', array($admin, 'create_admin_bing_console_page'));
677 }
678
679 }
680
681 /**
682 * Render the grouped navigation menu with Dashboard + SEO/Plugin dropdowns.
683 */
684 public function render_navigation_menu($current_page = null)
685 {
686 $page_slug = Metasync_Admin::$page_slug;
687 $available_menu_items = $this->get_available_menu_items();
688
689 $menu_icons = [
690 'general' => '⚙️',
691 'dashboard' => '📊',
692 'compatibility' => '🔧',
693 'sync_log' => '📋',
694 'seo_controls' => '🔍',
695 'optimal_settings' => '🚀',
696 'instant_index' => '🔗',
697 'google_console' => '📊',
698 'bing_console' => '📊',
699 'redirections' => '↩️',
700 'robots_txt' => '🤖',
701 'xml_sitemap' => '🗺️',
702 'import_seo' => '📥',
703 'custom_pages' => '📝',
704 'bot_statistics' => '🤖',
705 'report_issue' => '📝',
706 'error_log' => '⚠️'
707 ];
708
709 $seo_items = [];
710 $plugin_items = [];
711
712 foreach ($available_menu_items as $key => $menu_item) {
713 $group = $menu_item['group'] ?? 'plugin';
714 if ($group === 'seo') {
715 $seo_items[$key] = $menu_item;
716 } else {
717 $plugin_items[$key] = $menu_item;
718 }
719 }
720 ?>
721 <!-- Plugin Navigation Menu with Dashboard + Grouped Dropdowns -->
722 <div class="metasync-nav-wrapper">
723 <div class="metasync-nav-tabs metasync-nav-grouped">
724 <?php
725 // Dashboard tab (standalone)
726 if (isset($seo_items['dashboard'])) {
727 $is_active = ($current_page === 'dashboard');
728 $icon = $menu_icons['dashboard'] ?? '📊';
729 $page_url = '?page=' . $page_slug . $seo_items['dashboard']['slug_suffix'];
730 ?>
731 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
732 <span class="tab-icon"><?php echo $icon; ?></span>
733 <span class="tab-text"><?php echo esc_html($seo_items['dashboard']['title']); ?></span>
734 </a>
735 <?php
736 }
737 ?>
738
739 <!-- SEO Features Dropdown (excluding Dashboard) -->
740 <div class="metasync-nav-dropdown">
741 <?php
742 $has_active_seo = false;
743 foreach ($seo_items as $key => $menu_item) {
744 if ($key !== 'dashboard' && $current_page === $key) {
745 $has_active_seo = true;
746 break;
747 }
748 }
749 ?>
750 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_seo ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
751 <span class="tab-icon">🔍</span>
752 <span class="tab-text">SEO</span>
753 <span class="dropdown-arrow"></span>
754 </button>
755 <div class="metasync-nav-dropdown-menu">
756 <?php
757 foreach ($seo_items as $key => $menu_item) {
758 if ($key === 'dashboard') {
759 continue;
760 }
761
762 $is_active = ($current_page === $key);
763 $icon = $menu_icons[$key] ?? '📄';
764 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
765 ?>
766 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
767 <span class="tab-icon"><?php echo $icon; ?></span>
768 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
769 </a>
770 <?php
771 }
772 ?>
773 </div>
774 </div>
775
776 <!-- Plugin Dropdown -->
777 <div class="metasync-nav-dropdown">
778 <?php
779 $has_active_plugin = false;
780 foreach ($plugin_items as $key => $menu_item) {
781 if ($key !== 'report_issue' && $current_page === $key) {
782 $has_active_plugin = true;
783 break;
784 }
785 }
786 ?>
787 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_plugin ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
788 <span class="tab-icon">⚙️</span>
789 <span class="tab-text">Plugin</span>
790 <span class="dropdown-arrow"></span>
791 </button>
792 <div class="metasync-nav-dropdown-menu">
793 <?php
794 foreach ($plugin_items as $key => $menu_item) {
795 if ($key === 'report_issue') {
796 continue;
797 }
798
799 $is_active = ($current_page === $key);
800 $icon = $menu_icons[$key] ?? '📄';
801 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
802 ?>
803 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
804 <span class="tab-icon"><?php echo $icon; ?></span>
805 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
806 </a>
807 <?php
808 }
809 ?>
810 </div>
811 </div>
812
813 <!-- Right side - Report Issue and Settings dropdown -->
814 <div class="metasync-nav-right">
815 <?php
816 if (isset($available_menu_items['report_issue'])) {
817 $is_active = ($current_page === 'report_issue');
818 $page_url = '?page=' . $page_slug . $available_menu_items['report_issue']['slug_suffix'];
819 ?>
820 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
821 <span class="tab-icon">📝</span>
822 <span class="tab-text">Report Issue</span>
823 </a>
824 <?php } ?>
825 <div class="metasync-simple-dropdown">
826 <button type="button" class="metasync-settings-btn" id="metasync-settings-btn" onclick="toggleSettingsMenuPortal(event)" aria-expanded="false">
827 <span class="tab-icon">⚙️</span>
828 <span class="tab-text">Settings</span>
829 <span class="dropdown-arrow"></span>
830 </button>
831 </div>
832 </div>
833 </div>
834 </div>
835
836 <script>
837 // Handle navigation dropdowns using portal pattern for reliable positioning
838 (function() {
839 var dropdowns = document.querySelectorAll('.metasync-nav-dropdown');
840 var activePortal = null;
841 var activeButton = null;
842 var activeDropdown = null;
843 var scrollHandler = null;
844 var resizeHandler = null;
845
846 function positionPortalMenu(button, portalMenu) {
847 var rect = button.getBoundingClientRect();
848 var menuRect = portalMenu.getBoundingClientRect();
849 var viewportWidth = window.innerWidth;
850 var viewportHeight = window.innerHeight;
851
852 var left = rect.left;
853 if (left + menuRect.width > viewportWidth - 10) {
854 left = Math.max(10, viewportWidth - menuRect.width - 10);
855 }
856
857 var top = rect.bottom + 8;
858 if (top + menuRect.height > viewportHeight - 10 && rect.top > menuRect.height + 10) {
859 top = rect.top - menuRect.height - 8;
860 }
861
862 portalMenu.style.position = 'fixed';
863 portalMenu.style.top = top + 'px';
864 portalMenu.style.left = left + 'px';
865 portalMenu.style.zIndex = '999999999';
866 }
867
868 function closeActivePortal() {
869 if (activePortal && activePortal.parentNode) {
870 activePortal.parentNode.removeChild(activePortal);
871 }
872 if (activeButton) {
873 activeButton.setAttribute('aria-expanded', 'false');
874 }
875 if (activeDropdown) {
876 activeDropdown.classList.remove('active');
877 }
878 if (scrollHandler) {
879 window.removeEventListener('scroll', scrollHandler, true);
880 scrollHandler = null;
881 }
882 if (resizeHandler) {
883 window.removeEventListener('resize', resizeHandler);
884 resizeHandler = null;
885 }
886 activePortal = null;
887 activeButton = null;
888 activeDropdown = null;
889 }
890
891 function openAsPortal(dropdown, button, menu) {
892 closeActivePortal();
893
894 var portalMenu = menu.cloneNode(true);
895 portalMenu.id = 'metasync-nav-portal-menu';
896 portalMenu.style.opacity = '1';
897 portalMenu.style.visibility = 'visible';
898 portalMenu.style.transform = 'none';
899
900 document.body.appendChild(portalMenu);
901
902 positionPortalMenu(button, portalMenu);
903
904 activePortal = portalMenu;
905 activeButton = button;
906 activeDropdown = dropdown;
907 dropdown.classList.add('active');
908 button.setAttribute('aria-expanded', 'true');
909
910 scrollHandler = function() {
911 if (activePortal && activeButton) {
912 positionPortalMenu(activeButton, activePortal);
913 }
914 };
915 resizeHandler = scrollHandler;
916
917 window.addEventListener('scroll', scrollHandler, true);
918 window.addEventListener('resize', resizeHandler);
919
920 portalMenu.addEventListener('click', function(e) {
921 var link = e.target.closest('a');
922 if (link) {
923 setTimeout(closeActivePortal, 0);
924 }
925 });
926 }
927
928 dropdowns.forEach(function(dropdown) {
929 var button = dropdown.querySelector('.metasync-nav-dropdown-btn');
930 var menu = dropdown.querySelector('.metasync-nav-dropdown-menu');
931
932 if (!button || !menu) return;
933
934 button.addEventListener('click', function(e) {
935 e.preventDefault();
936 e.stopPropagation();
937
938 var isExpanded = button.getAttribute('aria-expanded') === 'true';
939
940 if (isExpanded) {
941 closeActivePortal();
942 } else {
943 openAsPortal(dropdown, button, menu);
944 }
945 });
946 });
947
948 document.addEventListener('click', function(e) {
949 if (activePortal && activeButton) {
950 if (!activePortal.contains(e.target) && !activeButton.contains(e.target)) {
951 closeActivePortal();
952 }
953 }
954 });
955
956 document.addEventListener('keydown', function(e) {
957 if (e.key === 'Escape' && activePortal) {
958 closeActivePortal();
959 if (activeButton) {
960 activeButton.focus();
961 }
962 }
963 });
964 })();
965
966 // Portal-style dropdown that bypasses stacking contexts
967 function toggleSettingsMenuPortal(event) {
968 event.preventDefault();
969 event.stopPropagation();
970
971 var button = event.currentTarget;
972 var existingMenu = document.getElementById('metasync-portal-menu');
973
974 if (existingMenu) {
975 existingMenu.remove();
976 button.classList.remove('active');
977 button.setAttribute('aria-expanded', 'false');
978 return;
979 }
980
981 var menu = document.createElement('div');
982 menu.id = 'metasync-portal-menu';
983 menu.className = 'metasync-portal-menu';
984
985 var currentUrl = window.location.href;
986 var isGeneralActive = currentUrl.indexOf('tab=general') > -1 || currentUrl.indexOf('tab=') === -1;
987 var isAdvancedActive = currentUrl.indexOf('tab=advanced') > -1;
988 var isWhitelabelActive = currentUrl.indexOf('tab=whitelabel') > -1;
989
990 menu.textContent = '';
991
992 var hideAdvanced = <?php
993 echo !Metasync_Access_Control::user_can_access('hide_advanced') ? 'true' : 'false';
994 ?>;
995 var showGeneral = <?php
996 echo Metasync_Access_Control::user_can_access('hide_settings') ? 'true' : 'false';
997 ?>;
998
999 if (showGeneral) {
1000 const generalLink = document.createElement('a');
1001 generalLink.href = '?page=<?php echo $page_slug; ?>&tab=general';
1002 generalLink.className = 'metasync-portal-item' + (isGeneralActive ? ' active' : '');
1003 generalLink.textContent = 'General';
1004 menu.appendChild(generalLink);
1005 }
1006
1007 const whitelabelLink = document.createElement('a');
1008 whitelabelLink.href = '?page=<?php echo $page_slug; ?>&tab=whitelabel';
1009 whitelabelLink.className = 'metasync-portal-item' + (isWhitelabelActive ? ' active' : '');
1010 whitelabelLink.textContent = 'White label';
1011 menu.appendChild(whitelabelLink);
1012
1013 if (!hideAdvanced) {
1014 const advancedLink = document.createElement('a');
1015 advancedLink.href = '?page=<?php echo $page_slug; ?>&tab=advanced';
1016 advancedLink.className = 'metasync-portal-item' + (isAdvancedActive ? ' active' : '');
1017 advancedLink.textContent = 'Advanced';
1018 menu.appendChild(advancedLink);
1019 }
1020
1021
1022 var rect = button.getBoundingClientRect();
1023 menu.style.position = 'fixed';
1024 menu.style.top = (rect.bottom + 8) + 'px';
1025 menu.style.right = (window.innerWidth - rect.right) + 'px';
1026 menu.style.zIndex = '999999999';
1027
1028 document.body.appendChild(menu);
1029
1030 button.classList.add('active');
1031 button.setAttribute('aria-expanded', 'true');
1032 }
1033
1034 document.addEventListener('click', function(event) {
1035 var button = document.getElementById('metasync-settings-btn');
1036 var menu = document.getElementById('metasync-portal-menu');
1037
1038 if (menu && button && !button.contains(event.target) && !menu.contains(event.target)) {
1039 menu.remove();
1040 button.classList.remove('active');
1041 button.setAttribute('aria-expanded', 'false');
1042 }
1043 });
1044 </script>
1045 <?php
1046 }
1047
1048 /**
1049 * Render the plugin header with logo, theme toggle, and connection badge.
1050 */
1051 public function render_plugin_header($page_title = null)
1052 {
1053 $general_settings = Metasync::get_option('general');
1054 $whitelabel_settings = Metasync::get_whitelabel_settings();
1055
1056 $whitelabel_logo = Metasync::get_whitelabel_logo();
1057 $is_whitelabel = isset($whitelabel_settings['is_whitelabel']) ? $whitelabel_settings['is_whitelabel'] : false;
1058
1059 $effective_plugin_name = Metasync::get_effective_plugin_name();
1060
1061 $display_title = $page_title ?: $effective_plugin_name;
1062
1063 $show_logo = false;
1064 $logo_url = '';
1065
1066 if (!empty($whitelabel_logo) && filter_var($whitelabel_logo, FILTER_VALIDATE_URL)) {
1067 $show_logo = true;
1068 $logo_url = esc_url($whitelabel_logo);
1069 } elseif (!$is_whitelabel) {
1070 $show_logo = true;
1071 $logo_url = Metasync::HOMEPAGE_DOMAIN . '/wp-content/uploads/2023/12/white.svg';
1072 } else {
1073 $show_logo = false;
1074 $logo_url = '';
1075 }
1076
1077 $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
1078 $otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : '';
1079
1080 $is_integrated = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1081
1082 $current_theme = get_option('metasync_theme', 'dark');
1083 ?>
1084
1085 <!-- Plugin Header with Logo -->
1086 <div class="metasync-header" data-current-theme="<?php echo esc_attr($current_theme); ?>">
1087 <div class="metasync-header-left">
1088 <?php if ($show_logo && !empty($logo_url)): ?>
1089 <div class="metasync-logo-container">
1090 <img src="<?php echo $logo_url; ?>" alt="Logo" class="metasync-logo" />
1091 </div>
1092 <?php endif; ?>
1093 </div>
1094
1095 <div class="metasync-header-right">
1096 <!-- Theme Toggle -->
1097 <div class="metasync-theme-toggle" role="group" aria-label="Theme Selector">
1098 <button class="metasync-theme-option <?php echo ($current_theme === 'light') ? 'active' : ''; ?>" data-theme="light" aria-label="Light Theme" type="button">
1099 <span class="metasync-theme-icon">☀️</span>
1100 <span class="theme-label">Light</span>
1101 </button>
1102 <button class="metasync-theme-option <?php echo ($current_theme === 'dark') ? 'active' : ''; ?>" data-theme="dark" aria-label="Dark Theme" type="button">
1103 <span class="metasync-theme-icon">🌙</span>
1104 <span class="theme-label">Dark</span>
1105 </button>
1106 </div>
1107
1108 <!-- Integration Status -->
1109 <?php
1110 if ($is_integrated && !empty($otto_pixel_uuid)) {
1111 $status_class_header = 'integrated';
1112 $status_title_header = 'Synced - Heartbeat API connectivity verified';
1113 $status_text_header = 'Synced';
1114 } elseif ($is_integrated && empty($otto_pixel_uuid)) {
1115 $status_class_header = 'warning';
1116 $status_title_header = 'Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1117 $status_text_header = 'Warning';
1118 } else {
1119 $status_class_header = 'not-integrated';
1120 $status_title_header = 'Not Synced - Heartbeat API not responding or unreachable';
1121 $status_text_header = 'Not Synced';
1122 }
1123 ?>
1124 <div class="metasync-integration-status <?php echo $status_class_header; ?>"
1125 title="<?php echo esc_attr($status_title_header); ?>">
1126 <span class="status-indicator"></span>
1127 <span class="status-text"><?php echo esc_html($status_text_header); ?></span>
1128 </div>
1129 </div>
1130 </div>
1131
1132 <!-- Page Title Below Header -->
1133 <div class="metasync-page-title">
1134 <h1><?php echo esc_html($display_title); ?></h1>
1135 </div>
1136
1137 <?php
1138 }
1139
1140 // ------------------------------------------------------------------
1141 // Admin bar status indicator
1142 // ------------------------------------------------------------------
1143
1144 /**
1145 * Output inline CSS (and optional JS) for the admin-bar status node.
1146 */
1147 public function metasync_admin_bar_style()
1148 {
1149 if (!is_admin_bar_showing()) {
1150 return;
1151 }
1152
1153 # For backward compatibility, constant takes precedence.
1154 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1155 return;
1156 }
1157
1158
1159 # Check if admin bar status is enabled via setting
1160 $general_settings = Metasync::get_option('general');
1161 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1162 if (!$show_admin_bar) {
1163 return;
1164 }
1165 ?>
1166 <style type="text/css">
1167 #wp-admin-bar-searchatlas-status .ab-item {
1168 font-weight: 500 !important;
1169 transition: all 0.2s ease !important;
1170 }
1171
1172 #wp-admin-bar-searchatlas-status:hover .ab-item {
1173 background-color: rgba(255, 255, 255, 0.1) !important;
1174 }
1175
1176 #wp-admin-bar-searchatlas-status.searchatlas-synced .ab-item {
1177 color: #46b450 !important; /* WordPress green for text */
1178 }
1179
1180 #wp-admin-bar-searchatlas-status.searchatlas-not-synced .ab-item {
1181 color: #dc3232 !important; /* WordPress red for text */
1182 }
1183
1184 #wp-admin-bar-searchatlas-status.searchatlas-warning .ab-item {
1185 color: #ffb900 !important; /* WordPress yellow for warning */
1186 }
1187
1188 /* Ensure emojis maintain their natural colors */
1189 #wp-admin-bar-searchatlas-status .ab-item {
1190 filter: none !important;
1191 }
1192
1193 /* Make status visible but subtle */
1194 #wp-admin-bar-searchatlas-status .ab-item {
1195 opacity: 0.9;
1196 }
1197
1198 #wp-admin-bar-searchatlas-status:hover .ab-item {
1199 opacity: 1;
1200 }
1201 </style>
1202
1203 <?php
1204 $page_slug = Metasync_Admin::$page_slug;
1205 $is_plugin_page = (
1206 (isset($_GET['page']) && strpos($_GET['page'], $page_slug) !== false) ||
1207 (isset($_GET['page']) && strpos($_GET['page'], 'searchatlas') !== false)
1208 );
1209 if ($is_plugin_page):
1210 ?>
1211 <script type="text/javascript">
1212 // Pass PHP variables to JavaScript
1213 window.MetasyncConfig = {
1214 pluginName: '<?php echo esc_js(Metasync::get_effective_plugin_name()); ?>',
1215 ottoName: '<?php echo esc_js(Metasync::get_whitelabel_otto_name()); ?>'
1216 };
1217
1218 jQuery(document).ready(function($) {
1219
1220 // Function to sync admin bar status
1221 function syncAdminBarStatus() {
1222 var pluginPageStatus = $('.metasync-integration-status .status-text').text();
1223 var adminBarItem = $('#wp-admin-bar-searchatlas-status .ab-item');
1224 var adminBarContainer = $('#wp-admin-bar-searchatlas-status');
1225 var pluginName = window.MetasyncConfig.pluginName;
1226
1227 if (pluginPageStatus && adminBarItem.length) {
1228 var allClasses = 'searchatlas-synced searchatlas-not-synced searchatlas-warning';
1229
1230 // Helper to update emoji in admin bar
1231 function updateAdminBarEmoji(targetEmoji, targetSvgCode) {
1232 var emojiImg = adminBarItem.find('img.emoji');
1233 if (emojiImg.length > 0) {
1234 emojiImg.attr('alt', targetEmoji);
1235 var currentSrc = emojiImg.attr('src');
1236 var updatedSrc = currentSrc.replace(/1f7e2\.svg|1f534\.svg|1f7e1\.svg/, targetSvgCode + '.svg');
1237 emojiImg.attr('src', updatedSrc);
1238 } else {
1239 var newHtml = adminBarItem.html().replace(/🟢|🔴|🟡/, targetEmoji);
1240 if (!newHtml.includes(targetEmoji) && newHtml.includes(pluginName)) {
1241 newHtml = newHtml.replace(pluginName, pluginName + ' ' + targetEmoji);
1242 }
1243 adminBarItem.html(newHtml);
1244 }
1245 }
1246
1247 if (pluginPageStatus.includes('Synced') && !pluginPageStatus.includes('Not Synced')) {
1248 // Update admin bar to synced (GREEN)
1249 updateAdminBarEmoji('🟢', '1f7e2');
1250 adminBarContainer.removeClass(allClasses).addClass('searchatlas-synced');
1251 var syncTitle = pluginName + ' - Synced (Heartbeat API connectivity verified)';
1252 adminBarContainer.attr('title', syncTitle);
1253 adminBarItem.attr('title', syncTitle);
1254
1255 } else if (pluginPageStatus.includes('Warning')) {
1256 // Update admin bar to warning (YELLOW)
1257 updateAdminBarEmoji('🟡', '1f7e1');
1258 adminBarContainer.removeClass(allClasses).addClass('searchatlas-warning');
1259 var warnTitle = pluginName + ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1260 adminBarContainer.attr('title', warnTitle);
1261 adminBarItem.attr('title', warnTitle);
1262
1263 } else if (pluginPageStatus.includes('Not Synced')) {
1264 // Update admin bar to not synced (RED)
1265 updateAdminBarEmoji('🔴', '1f534');
1266 adminBarContainer.removeClass(allClasses).addClass('searchatlas-not-synced');
1267 var notSyncTitle = pluginName + ' - Not Synced (Heartbeat API not responding or unreachable)';
1268 adminBarContainer.attr('title', notSyncTitle);
1269 adminBarItem.attr('title', notSyncTitle);
1270 }
1271 }
1272 }
1273
1274 // Sync when tabs are switched (for General/Advanced tabs)
1275 $(document).on('click', 'a[href*="tab="]', function() {
1276 setTimeout(syncAdminBarStatus, 200);
1277 });
1278
1279 // Also check every 5 seconds to keep it in sync
1280 setInterval(syncAdminBarStatus, 5000);
1281 });
1282 </script>
1283 <?php endif; ?>
1284 <?php
1285 }
1286
1287 /**
1288 * Add Search Atlas status indicator to WordPress admin bar.
1289 */
1290 public function add_searchatlas_admin_bar_status($wp_admin_bar)
1291 {
1292 if (!Metasync::current_user_has_plugin_access()) {
1293 return;
1294 }
1295
1296 # For backward compatibility, constant takes precedence.
1297 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1298 return;
1299 }
1300
1301 # Check if admin bar status is disabled via setting
1302 $general_settings = Metasync::get_option('general');
1303 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1304 if (!$show_admin_bar) {
1305 return;
1306 }
1307
1308 if (!Metasync::current_user_has_plugin_access()) {
1309 return;
1310 }
1311
1312 if (!is_admin() && !apply_filters('metasync_show_admin_bar_status_frontend', false)) {
1313 return;
1314 }
1315
1316 $general_settings = Metasync::get_option('general');
1317 if (!is_array($general_settings)) {
1318 $general_settings = [];
1319 }
1320
1321 $is_synced = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1322
1323 $plugin_name = Metasync::get_effective_plugin_name();
1324
1325 if ($is_synced) {
1326 $otto_uuid = $general_settings['otto_pixel_uuid'] ?? '';
1327 if (!empty($otto_uuid)) {
1328 $status_emoji = '🟢'; // Green circle for synced
1329 $title = $plugin_name . ' - Synced (Heartbeat API connectivity verified)';
1330 $status_class = 'searchatlas-synced';
1331 } else {
1332 $status_emoji = '🟡'; // Yellow circle for warning
1333 $title = $plugin_name . ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1334 $status_class = 'searchatlas-warning';
1335 }
1336 } else {
1337 $status_emoji = '🔴'; // Red circle for not synced
1338 $title = $plugin_name . ' - Not Synced (Heartbeat API not responding or unreachable)';
1339 $status_class = 'searchatlas-not-synced';
1340 }
1341
1342 $display_name = $plugin_name;
1343 $admin_bar_title = $display_name . ' ' . $status_emoji;
1344
1345 $wp_admin_bar->add_node(array(
1346 'id' => 'searchatlas-status',
1347 'title' => $admin_bar_title,
1348 'href' => admin_url('admin.php?page=' . Metasync_Admin::$page_slug),
1349 'meta' => array(
1350 'title' => $title,
1351 'class' => $status_class
1352 )
1353 ));
1354 }
1355 }
1356