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

1,839 lines 93.9 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 // Custom HTML Pages (check access control)
561 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
562 $menu_items['custom_pages'] = [
563 'title' => 'Custom Pages',
564 'slug_suffix' => '-custom-pages',
565 'callback' => 'create_admin_custom_pages_page',
566 'internal_nav' => 'Custom HTML Pages',
567 'group' => 'plugin'
568 ];
569 }
570
571 if ($general_options['enable_optimal_settings'] ?? false) {
572 $menu_items['optimal_settings'] = [
573 'title' => 'Optimal Settings',
574 'slug_suffix' => '-optimal-settings',
575 'callback' => 'create_admin_optimal_settings_page',
576 'internal_nav' => 'Optimal Settings',
577 'group' => 'plugin'
578 ];
579 }
580
581 // Compatibility page (check access control)
582 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
583 $menu_items['compatibility'] = [
584 'title' => 'Compatibility',
585 'slug_suffix' => '-compatibility',
586 'callback' => 'create_admin_compatibility_page',
587 'internal_nav' => 'Compatibility',
588 'group' => 'plugin'
589 ];
590 }
591
592 // Sync Log page (check access control)
593 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
594 $menu_items['sync_log'] = [
595 'title' => 'Changes Log',
596 'slug_suffix' => '-sync-log',
597 'callback' => 'create_admin_sync_log_page',
598 'internal_nav' => 'Changes Log',
599 'group' => 'plugin'
600 ];
601 }
602
603 // Bot Statistics
604 $menu_items['bot_statistics'] = [
605 'title' => 'Bot Statistics',
606 'slug_suffix' => '-bot-statistics',
607 'callback' => 'create_admin_bot_statistics_page',
608 'internal_nav' => 'Bot Statistics',
609 'group' => 'plugin'
610 ];
611
612 // Report Issue page (check access control)
613 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
614 $menu_items['report_issue'] = [
615 'title' => 'Report Issue',
616 'slug_suffix' => '-report-issue',
617 'callback' => 'create_admin_report_issue_page',
618 'internal_nav' => 'Report Issue',
619 'group' => 'plugin'
620 ];
621 }
622
623 return $menu_items;
624 }
625
626 /**
627 * Register all WordPress admin menu / submenu pages.
628 *
629 * @param Metasync_Admin $admin The admin instance whose callbacks WordPress will invoke.
630 */
631 public function add_plugin_settings_page($admin)
632 {
633 if (!Metasync::current_user_has_plugin_access()) {
634 return;
635 }
636
637 $data = Metasync::get_option('general');
638 $plugin_name = Metasync::get_effective_plugin_name();
639 $menu_name = $plugin_name;
640 $menu_title = $plugin_name;
641 $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'];
642 $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'];
643
644 // Use 'read' capability since actual access is controlled by current_user_has_plugin_access() check above
645 $menu_capability = 'read';
646
647 // Separator just before the plugin entry, after all other plugins (Yoast etc. ~99)
648 add_action('admin_menu', function() {
649 global $menu;
650 $menu['100'] = array('', 'read', 'separator-metasync-before', '', 'wp-menu-separator');
651 }, 5);
652
653 // Main menu page at position 100.1 — bottom of the menu after all standard items
654 add_menu_page(
655 $menu_name,
656 $menu_title,
657 $menu_capability,
658 $menu_slug,
659 array($admin, 'create_admin_settings_page'),
660 $menu_icon,
661 '100.1'
662 );
663
664 // Check connection status for submenu availability
665 $general_options = Metasync::get_option('general');
666 $has_api_key = !empty($general_options['searchatlas_api_key']);
667 $has_uuid = !empty($general_options['otto_pixel_uuid']);
668 $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options);
669
670 $seo_controls = Metasync::get_option('seo_controls');
671
672 // ── Connect slug ──────────────────────────────────────────────────
673 $connect_slug = $menu_slug . '-connect';
674
675 // Dashboard
676 if (Metasync_Access_Control::user_can_access('hide_dashboard')) {
677 add_submenu_page($menu_slug, 'Dashboard', 'Dashboard', $menu_capability, $menu_slug . '-dashboard', array($admin, 'create_admin_dashboard_iframe'));
678 }
679
680 // Indexation Control
681 if (Metasync_Access_Control::user_can_access('hide_indexation_control')) {
682 add_submenu_page($menu_slug, 'Indexation Control', 'Indexation Control', $menu_capability, $menu_slug . '-seo-controls', array($admin, 'create_admin_seo_controls_page'));
683 }
684
685 // 404 Monitor
686 add_submenu_page($menu_slug, '404 Monitor', '404 Monitor', $menu_capability, $menu_slug . '-404-monitor', array($admin, 'create_admin_404_monitor_page'));
687
688 // Redirections
689 if (Metasync_Access_Control::user_can_access('hide_redirections')) {
690 add_submenu_page($menu_slug, 'Redirections', 'Redirections', $menu_capability, $menu_slug . '-redirections', array($admin, 'create_admin_redirections_page'));
691 }
692
693 // XML Sitemap
694 if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) {
695 add_submenu_page($menu_slug, 'XML Sitemap', 'XML Sitemap', $menu_capability, $menu_slug . '-xml-sitemap', array($admin, 'create_admin_xml_sitemap_page'));
696 }
697
698 // Robots.txt
699 if (Metasync_Access_Control::user_can_access('hide_robots')) {
700 add_submenu_page($menu_slug, 'Robots.txt', 'Robots.txt', $menu_capability, $menu_slug . '-robots-txt', array($admin, 'create_admin_robots_txt_page'));
701 }
702
703 // Site Verification
704 add_submenu_page($menu_slug, 'Site Verification', 'Site Verification', $menu_capability, $menu_slug . '-search-engine-verify', array($admin, 'create_admin_search_engine_verification_page'));
705
706 // Instant Indexing (conditional)
707 if ($seo_controls['enable_googleinstantindex'] ?? false) {
708 add_submenu_page($menu_slug, 'Instant Indexing', 'Instant Indexing', $menu_capability, $menu_slug . '-instant-index', array($admin, 'create_admin_google_instant_index_page'));
709 }
710
711 // Google Console (conditional)
712 if ($general_options['enable_google_console'] ?? false) {
713 add_submenu_page($menu_slug, 'Google Console', 'Google Console', $menu_capability, $menu_slug . '-google-console', array($admin, 'create_admin_google_console_page'));
714 }
715
716 // Bing Console (conditional)
717 if ($seo_controls['enable_binginstantindex'] ?? false) {
718 add_submenu_page($menu_slug, 'Bing Console', 'Bing Console', $menu_capability, $menu_slug . '-bing-console', array($admin, 'create_admin_bing_console_page'));
719 }
720
721 // Schema Markup
722 add_submenu_page($menu_slug, 'Schema Markup', 'Schema Markup', $menu_capability, $menu_slug . '-schema-markup', array($admin, 'create_admin_schema_markup_page'));
723
724 // Import SEO Data
725 if (Metasync_Access_Control::user_can_access('hide_import_seo')) {
726 add_submenu_page($menu_slug, 'Import SEO Data', 'Import SEO Data', $menu_capability, $menu_slug . '-import-external', array($admin, 'render_import_external_data_page'));
727 }
728
729 // Settings
730 if (Metasync_Access_Control::user_can_access('hide_settings')) {
731 add_submenu_page($menu_slug, 'Settings', 'Settings', $menu_capability, $menu_slug, array($admin, 'create_admin_settings_page'));
732 }
733
734 // Local Business
735 add_submenu_page($menu_slug, 'Local Business', 'Local Business', $menu_capability, $menu_slug . '-local-business', array($admin, 'create_admin_local_business_page'));
736
737 // Breadcrumbs
738 add_submenu_page($menu_slug, 'Breadcrumbs', 'Breadcrumbs', $menu_capability, $menu_slug . '-breadcrumbs', array($admin, 'create_admin_breadcrumbs_page'));
739
740 // Code Snippets
741 add_submenu_page($menu_slug, 'Code Snippets', 'Code Snippets', $menu_capability, $menu_slug . '-code-snippets', array($admin, 'create_admin_code_snippets_page'));
742
743 // Code Minification
744 add_submenu_page($menu_slug, 'Code Minification', 'Code Minification', $menu_capability, $menu_slug . '-code-minification', array($admin, 'create_admin_code_minification_page'));
745
746 // Custom Pages
747 if (Metasync_Access_Control::user_can_access('hide_custom_pages')) {
748 add_submenu_page($menu_slug, 'Custom Pages', 'Custom Pages', $menu_capability, $menu_slug . '-custom-pages', array($admin, 'create_admin_custom_pages_page'));
749 }
750
751 // Bot Statistics
752 add_submenu_page($menu_slug, 'Bot Statistics', 'Bot Statistics', $menu_capability, $menu_slug . '-bot-statistics', array($admin, 'create_admin_bot_statistics_page'));
753
754 // SEO Health dashboard
755 add_submenu_page($menu_slug, 'SEO Health', 'SEO Health', $menu_capability, $menu_slug . '-seo-health', array($admin, 'create_admin_seo_health_page'));
756
757 // Compatibility
758 if (Metasync_Access_Control::user_can_access('hide_compatibility')) {
759 add_submenu_page($menu_slug, 'Compatibility', 'Compatibility', $menu_capability, $menu_slug . '-compatibility', array($admin, 'create_admin_compatibility_page'));
760 }
761
762 // Changes Log
763 if (Metasync_Access_Control::user_can_access('hide_sync_log')) {
764 add_submenu_page($menu_slug, 'Changes Log', 'Changes Log', $menu_capability, $menu_slug . '-sync-log', array($admin, 'create_admin_sync_log_page'));
765 }
766
767 // Report Issue
768 if (Metasync_Access_Control::user_can_access('hide_report_issue')) {
769 add_submenu_page($menu_slug, 'Report Issue', 'Report Issue', $menu_capability, $menu_slug . '-report-issue', array($admin, 'create_admin_report_issue_page'));
770 }
771
772 // ── Connect CTA (shown when not authenticated) ────────────────────
773 if (!$is_fully_connected) {
774 add_submenu_page($menu_slug, 'Connect to SearchAtlas', 'Connect to SearchAtlas', $menu_capability, $connect_slug, array($admin, 'create_admin_settings_page'));
775 }
776
777 // ── Hidden pages (no sidebar entry needed) ────────────────────────
778 add_submenu_page('', 'Setup Wizard', 'Setup Wizard', $menu_capability, $menu_slug . '-setup-wizard', array($admin->setup_wizard, 'render_wizard_page'));
779
780
781 // Rename auto-generated first submenu from plugin name to "Settings" and reorder
782 add_action('admin_menu', function() use ($menu_slug) {
783 global $submenu;
784 if (!isset($submenu[$menu_slug])) {
785 return;
786 }
787 // Remove the auto-duplicate of the main menu item (same slug as parent)
788 foreach ($submenu[$menu_slug] as $key => $item) {
789 if ($item[2] === $menu_slug) {
790 unset($submenu[$menu_slug][$key]);
791 break;
792 }
793 }
794 }, 999);
795
796 }
797
798 /**
799 * Render the grouped navigation menu with Dashboard + SEO/Plugin dropdowns.
800 */
801 public function render_navigation_menu($current_page = null)
802 {
803 $page_slug = Metasync_Admin::$page_slug;
804 $available_menu_items = $this->get_available_menu_items();
805
806 $menu_icons = [
807 'general' => 'admin-settings',
808 'dashboard' => 'dashboard',
809 'compatibility' => 'admin-tools',
810 'sync_log' => 'list-view',
811 'seo_controls' => 'search',
812 'optimal_settings' => 'superhero-alt',
813 'instant_index' => 'performance',
814 'google_console' => 'chart-area',
815 'bing_console' => 'chart-bar',
816 'redirections' => 'undo',
817 'robots_txt' => 'shield',
818 'xml_sitemap' => 'networking',
819 'schema_markup' => 'tag',
820 'site_verification'=> 'yes-alt',
821 'import_seo' => 'download',
822 'custom_pages' => 'admin-page',
823 'code_minification'=> 'media-code',
824 'bot_statistics' => 'visibility',
825 'breadcrumbs' => 'menu',
826 'monitor_404' => 'warning',
827 'local_business' => 'building',
828 'code_snippets' => 'editor-code',
829 'report_issue' => 'sos',
830 'error_log' => 'warning',
831 'seo_health' => 'heart'
832
833 ];
834
835 $seo_items = [];
836 $plugin_items = [];
837
838 foreach ($available_menu_items as $key => $menu_item) {
839 $group = $menu_item['group'] ?? 'plugin';
840 if ($group === 'seo') {
841 $seo_items[$key] = $menu_item;
842 } else {
843 $plugin_items[$key] = $menu_item;
844 }
845 }
846 ?>
847 <!-- Plugin Navigation Menu with Dashboard + Grouped Dropdowns -->
848 <div class="metasync-nav-wrapper">
849 <div class="metasync-nav-tabs metasync-nav-grouped">
850 <?php
851 // Dashboard tab (standalone)
852 if (isset($seo_items['dashboard'])) {
853 $is_active = ($current_page === 'dashboard');
854 $icon = $menu_icons['dashboard'] ?? 'admin-generic';
855 $page_url = '?page=' . $page_slug . $seo_items['dashboard']['slug_suffix'];
856 ?>
857 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
858 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
859 <span class="tab-text"><?php echo esc_html($seo_items['dashboard']['title']); ?></span>
860 </a>
861 <?php
862 }
863 ?>
864
865 <!-- SEO Features Dropdown (excluding Dashboard) -->
866 <div class="metasync-nav-dropdown">
867 <?php
868 $has_active_seo = false;
869 foreach ($seo_items as $key => $menu_item) {
870 if ($key !== 'dashboard' && $current_page === $key) {
871 $has_active_seo = true;
872 break;
873 }
874 }
875 ?>
876 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_seo ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
877 <span class="tab-icon"><span class="dashicons dashicons-search"></span></span>
878 <span class="tab-text">SEO</span>
879 <span class="dropdown-arrow"></span>
880 </button>
881 <div class="metasync-nav-dropdown-menu">
882 <?php
883 foreach ($seo_items as $key => $menu_item) {
884 if ($key === 'dashboard') {
885 continue;
886 }
887
888 $is_active = ($current_page === $key);
889 $icon = $menu_icons[$key] ?? 'admin-generic';
890 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
891 ?>
892 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
893 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
894 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
895 </a>
896 <?php
897 }
898 ?>
899 </div>
900 </div>
901
902 <!-- Plugin Dropdown -->
903 <div class="metasync-nav-dropdown">
904 <?php
905 $has_active_plugin = false;
906 foreach ($plugin_items as $key => $menu_item) {
907 if ($key !== 'report_issue' && $current_page === $key) {
908 $has_active_plugin = true;
909 break;
910 }
911 }
912 ?>
913 <button type="button" class="metasync-nav-dropdown-btn <?php echo $has_active_plugin ? 'active' : ''; ?>" aria-haspopup="true" aria-expanded="false">
914 <span class="tab-icon"><span class="dashicons dashicons-admin-settings"></span></span>
915 <span class="tab-text">Plugin</span>
916 <span class="dropdown-arrow"></span>
917 </button>
918 <div class="metasync-nav-dropdown-menu">
919 <?php
920 foreach ($plugin_items as $key => $menu_item) {
921 if ($key === 'report_issue') {
922 continue;
923 }
924
925 $is_active = ($current_page === $key);
926 $icon = $menu_icons[$key] ?? 'admin-generic';
927 $page_url = '?page=' . $page_slug . $menu_item['slug_suffix'];
928 ?>
929 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
930 <span class="tab-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
931 <span class="tab-text"><?php echo esc_html($menu_item['title']); ?></span>
932 </a>
933 <?php
934 }
935 ?>
936 </div>
937 </div>
938
939 <!-- Right side - Report Issue and Settings dropdown -->
940 <div class="metasync-nav-right">
941 <?php
942 if (isset($available_menu_items['report_issue'])) {
943 $is_active = ($current_page === 'report_issue');
944 $page_url = '?page=' . $page_slug . $available_menu_items['report_issue']['slug_suffix'];
945 ?>
946 <a href="<?php echo esc_url($page_url); ?>" class="metasync-nav-tab <?php echo $is_active ? 'active' : ''; ?>">
947 <span class="tab-icon"><span class="dashicons dashicons-sos"></span></span>
948 <span class="tab-text">Report Issue</span>
949 </a>
950 <?php } ?>
951 <div class="metasync-simple-dropdown">
952 <button type="button" class="metasync-settings-btn" id="metasync-settings-btn" onclick="toggleSettingsMenuPortal(event)" aria-expanded="false">
953 <span class="tab-icon"><span class="dashicons dashicons-admin-settings"></span></span>
954 <span class="tab-text">Settings</span>
955 <span class="dropdown-arrow"></span>
956 </button>
957 </div>
958 </div>
959 </div>
960 </div>
961
962 <script>
963 // Handle navigation dropdowns using portal pattern for reliable positioning
964 (function() {
965 var dropdowns = document.querySelectorAll('.metasync-nav-dropdown');
966 var activePortal = null;
967 var activeButton = null;
968 var activeDropdown = null;
969 var scrollHandler = null;
970 var resizeHandler = null;
971
972 function positionPortalMenu(button, portalMenu) {
973 var rect = button.getBoundingClientRect();
974 var menuRect = portalMenu.getBoundingClientRect();
975 var viewportWidth = window.innerWidth;
976 var viewportHeight = window.innerHeight;
977
978 var left = rect.left;
979 if (left + menuRect.width > viewportWidth - 10) {
980 left = Math.max(10, viewportWidth - menuRect.width - 10);
981 }
982
983 var top = rect.bottom + 8;
984 if (top + menuRect.height > viewportHeight - 10 && rect.top > menuRect.height + 10) {
985 top = rect.top - menuRect.height - 8;
986 }
987
988 portalMenu.style.position = 'fixed';
989 portalMenu.style.top = top + 'px';
990 portalMenu.style.left = left + 'px';
991 portalMenu.style.zIndex = '999999999';
992 }
993
994 function closeActivePortal() {
995 if (activePortal && activePortal.parentNode) {
996 activePortal.parentNode.removeChild(activePortal);
997 }
998 if (activeButton) {
999 activeButton.setAttribute('aria-expanded', 'false');
1000 }
1001 if (activeDropdown) {
1002 activeDropdown.classList.remove('active');
1003 }
1004 if (scrollHandler) {
1005 window.removeEventListener('scroll', scrollHandler, true);
1006 scrollHandler = null;
1007 }
1008 if (resizeHandler) {
1009 window.removeEventListener('resize', resizeHandler);
1010 resizeHandler = null;
1011 }
1012 activePortal = null;
1013 activeButton = null;
1014 activeDropdown = null;
1015 }
1016
1017 function openAsPortal(dropdown, button, menu) {
1018 closeActivePortal();
1019
1020 var portalMenu = menu.cloneNode(true);
1021 portalMenu.id = 'metasync-nav-portal-menu';
1022 portalMenu.style.opacity = '1';
1023 portalMenu.style.visibility = 'visible';
1024 portalMenu.style.transform = 'none';
1025
1026 document.body.appendChild(portalMenu);
1027
1028 positionPortalMenu(button, portalMenu);
1029
1030 activePortal = portalMenu;
1031 activeButton = button;
1032 activeDropdown = dropdown;
1033 dropdown.classList.add('active');
1034 button.setAttribute('aria-expanded', 'true');
1035
1036 scrollHandler = function() {
1037 if (activePortal && activeButton) {
1038 positionPortalMenu(activeButton, activePortal);
1039 }
1040 };
1041 resizeHandler = scrollHandler;
1042
1043 window.addEventListener('scroll', scrollHandler, true);
1044 window.addEventListener('resize', resizeHandler);
1045
1046 portalMenu.addEventListener('click', function(e) {
1047 var link = e.target.closest('a');
1048 if (link) {
1049 setTimeout(closeActivePortal, 0);
1050 }
1051 });
1052 }
1053
1054 dropdowns.forEach(function(dropdown) {
1055 var button = dropdown.querySelector('.metasync-nav-dropdown-btn');
1056 var menu = dropdown.querySelector('.metasync-nav-dropdown-menu');
1057
1058 if (!button || !menu) return;
1059
1060 button.addEventListener('click', function(e) {
1061 e.preventDefault();
1062 e.stopPropagation();
1063
1064 var isExpanded = button.getAttribute('aria-expanded') === 'true';
1065
1066 if (isExpanded) {
1067 closeActivePortal();
1068 } else {
1069 openAsPortal(dropdown, button, menu);
1070 }
1071 });
1072 });
1073
1074 document.addEventListener('click', function(e) {
1075 if (activePortal && activeButton) {
1076 if (!activePortal.contains(e.target) && !activeButton.contains(e.target)) {
1077 closeActivePortal();
1078 }
1079 }
1080 });
1081
1082 document.addEventListener('keydown', function(e) {
1083 if (e.key === 'Escape' && activePortal) {
1084 closeActivePortal();
1085 if (activeButton) {
1086 activeButton.focus();
1087 }
1088 }
1089 });
1090 })();
1091
1092 // Portal-style dropdown that bypasses stacking contexts
1093 function toggleSettingsMenuPortal(event) {
1094 event.preventDefault();
1095 event.stopPropagation();
1096
1097 var button = event.currentTarget;
1098 var existingMenu = document.getElementById('metasync-portal-menu');
1099
1100 if (existingMenu) {
1101 existingMenu.remove();
1102 button.classList.remove('active');
1103 button.setAttribute('aria-expanded', 'false');
1104 return;
1105 }
1106
1107 var menu = document.createElement('div');
1108 menu.id = 'metasync-portal-menu';
1109 menu.className = 'metasync-portal-menu';
1110
1111 var currentUrl = window.location.href;
1112 var isGeneralActive = currentUrl.indexOf('tab=general') > -1 || currentUrl.indexOf('tab=') === -1;
1113 var isAdvancedActive = currentUrl.indexOf('tab=advanced') > -1;
1114 var isWhitelabelActive = currentUrl.indexOf('tab=whitelabel') > -1;
1115
1116 menu.textContent = '';
1117
1118 var hideAdvanced = <?php
1119 echo !Metasync_Access_Control::user_can_access('hide_advanced') ? 'true' : 'false';
1120 ?>;
1121 var showGeneral = <?php
1122 echo Metasync_Access_Control::user_can_access('hide_settings') ? 'true' : 'false';
1123 ?>;
1124
1125 if (showGeneral) {
1126 const generalLink = document.createElement('a');
1127 generalLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=general';
1128 generalLink.className = 'metasync-portal-item' + (isGeneralActive ? ' active' : '');
1129 generalLink.textContent = 'General';
1130 menu.appendChild(generalLink);
1131 }
1132
1133 const whitelabelLink = document.createElement('a');
1134 whitelabelLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=whitelabel';
1135 whitelabelLink.className = 'metasync-portal-item' + (isWhitelabelActive ? ' active' : '');
1136 whitelabelLink.textContent = 'White label';
1137 menu.appendChild(whitelabelLink);
1138
1139 if (!hideAdvanced) {
1140 const advancedLink = document.createElement('a');
1141 advancedLink.href = '?page=<?php echo esc_js( $page_slug ); ?>&tab=advanced';
1142 advancedLink.className = 'metasync-portal-item' + (isAdvancedActive ? ' active' : '');
1143 advancedLink.textContent = 'Advanced';
1144 menu.appendChild(advancedLink);
1145 }
1146
1147
1148 var rect = button.getBoundingClientRect();
1149 menu.style.position = 'fixed';
1150 menu.style.top = (rect.bottom + 8) + 'px';
1151 menu.style.right = (window.innerWidth - rect.right) + 'px';
1152 menu.style.zIndex = '999999999';
1153
1154 document.body.appendChild(menu);
1155
1156 button.classList.add('active');
1157 button.setAttribute('aria-expanded', 'true');
1158 }
1159
1160 document.addEventListener('click', function(event) {
1161 var button = document.getElementById('metasync-settings-btn');
1162 var menu = document.getElementById('metasync-portal-menu');
1163
1164 if (menu && button && !button.contains(event.target) && !menu.contains(event.target)) {
1165 menu.remove();
1166 button.classList.remove('active');
1167 button.setAttribute('aria-expanded', 'false');
1168 }
1169 });
1170 </script>
1171 <?php
1172 }
1173
1174 /**
1175 * Render the plugin header with logo, theme toggle, and connection badge.
1176 */
1177 public function render_plugin_header($page_title = null)
1178 {
1179 $general_settings = Metasync::get_option('general');
1180
1181 $effective_plugin_name = Metasync::get_effective_plugin_name();
1182 $display_title = $page_title ?: $effective_plugin_name;
1183 $logo = $this->resolve_logo_data();
1184
1185 $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
1186 $otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : '';
1187
1188 $is_integrated = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1189
1190 $current_theme = get_option('metasync_theme', 'dark');
1191 ?>
1192
1193 <!-- Plugin Header with Logo -->
1194 <div class="metasync-header" data-current-theme="<?php echo esc_attr($current_theme); ?>">
1195 <div class="metasync-header-left">
1196 <?php if ($logo['show_logo'] && $logo['use_dual']): ?>
1197 <div class="metasync-logo-container">
1198 <img src="<?php echo esc_url($logo['light_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-light" />
1199 <img src="<?php echo esc_url($logo['dark_url']); ?>" alt="Logo" class="metasync-logo metasync-logo-dark" />
1200 </div>
1201 <?php elseif ($logo['show_logo'] && !empty($logo['url'])): ?>
1202 <div class="metasync-logo-container">
1203 <img src="<?php echo esc_url($logo['url']); ?>" alt="Logo" class="metasync-logo<?php echo !empty($logo['is_default']) ? ' metasync-logo-default' : ''; ?>" />
1204 </div>
1205 <?php endif; ?>
1206 </div>
1207
1208 <div class="metasync-header-right">
1209 <!-- Theme Toggle -->
1210 <div class="metasync-theme-toggle" role="group" aria-label="Theme Selector">
1211 <button class="metasync-theme-option <?php echo ($current_theme === 'light') ? 'active' : ''; ?>" data-theme="light" aria-label="Light Theme" type="button">
1212 <span class="metasync-theme-icon">&#9728;</span>
1213 <span class="theme-label">Light</span>
1214 </button>
1215 <button class="metasync-theme-option <?php echo ($current_theme === 'dark') ? 'active' : ''; ?>" data-theme="dark" aria-label="Dark Theme" type="button">
1216 <span class="metasync-theme-icon">&#9790;</span>
1217 <span class="theme-label">Dark</span>
1218 </button>
1219 </div>
1220
1221 <!-- Integration Status -->
1222 <?php
1223 if ($is_integrated && !empty($otto_pixel_uuid)) {
1224 $status_class_header = 'integrated';
1225 $status_title_header = 'Synced - Heartbeat API connectivity verified';
1226 $status_text_header = 'Synced';
1227 } elseif ($is_integrated && empty($otto_pixel_uuid)) {
1228 $status_class_header = 'warning';
1229 $status_title_header = 'Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1230 $status_text_header = 'Warning';
1231 } else {
1232 $status_class_header = 'not-integrated';
1233 $status_title_header = 'Not Synced - Heartbeat API not responding or unreachable';
1234 $status_text_header = 'Not Synced';
1235 }
1236 ?>
1237 <div class="metasync-integration-status <?php echo $status_class_header; ?>"
1238 title="<?php echo esc_attr($status_title_header); ?>">
1239 <span class="status-indicator"></span>
1240 <span class="status-text"><?php echo esc_html($status_text_header); ?></span>
1241 </div>
1242 </div>
1243 </div>
1244
1245 <!-- Page Title Below Header -->
1246 <div class="metasync-page-title">
1247 <h1><?php echo esc_html($display_title); ?></h1>
1248 </div>
1249
1250 <?php
1251 }
1252
1253 // ------------------------------------------------------------------
1254 // Admin bar status indicator
1255 // ------------------------------------------------------------------
1256
1257 /**
1258 * Output inline CSS (and optional JS) for the admin-bar status node.
1259 */
1260 public function metasync_admin_bar_style()
1261 {
1262 if (!is_admin_bar_showing()) {
1263 return;
1264 }
1265
1266 # For backward compatibility, constant takes precedence.
1267 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1268 return;
1269 }
1270
1271
1272 # Check if admin bar status is enabled via setting
1273 $general_settings = Metasync::get_option('general');
1274 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1275 if (!$show_admin_bar) {
1276 return;
1277 }
1278 ?>
1279 <style type="text/css">
1280 #wp-admin-bar-searchatlas-status .ab-item {
1281 font-weight: 500 !important;
1282 transition: all 0.2s ease !important;
1283 }
1284
1285 #wp-admin-bar-searchatlas-status:hover .ab-item {
1286 background-color: rgba(255, 255, 255, 0.1) !important;
1287 }
1288
1289 #wp-admin-bar-searchatlas-status.searchatlas-synced .ab-item {
1290 color: #46b450 !important; /* WordPress green for text */
1291 }
1292
1293 #wp-admin-bar-searchatlas-status.searchatlas-not-synced .ab-item {
1294 color: #dc3232 !important; /* WordPress red for text */
1295 }
1296
1297 #wp-admin-bar-searchatlas-status.searchatlas-warning .ab-item {
1298 color: #ffb900 !important; /* WordPress yellow for warning */
1299 }
1300
1301 /* Ensure emojis maintain their natural colors */
1302 #wp-admin-bar-searchatlas-status .ab-item {
1303 filter: none !important;
1304 }
1305
1306 /* Make status visible but subtle */
1307 #wp-admin-bar-searchatlas-status .ab-item {
1308 opacity: 0.9;
1309 }
1310
1311 #wp-admin-bar-searchatlas-status:hover .ab-item {
1312 opacity: 1;
1313 }
1314 </style>
1315
1316 <?php
1317 $page_slug = Metasync_Admin::$page_slug;
1318 $is_plugin_page = (
1319 (isset($_GET['page']) && strpos($_GET['page'], $page_slug) !== false) ||
1320 (isset($_GET['page']) && strpos($_GET['page'], 'searchatlas') !== false)
1321 );
1322 if ($is_plugin_page):
1323 ?>
1324 <script type="text/javascript">
1325 // Pass PHP variables to JavaScript
1326 window.MetasyncConfig = {
1327 pluginName: '<?php echo esc_js(Metasync::get_effective_plugin_name()); ?>',
1328 ottoName: '<?php echo esc_js(Metasync::get_whitelabel_otto_name()); ?>'
1329 };
1330
1331 jQuery(document).ready(function($) {
1332
1333 // Function to sync admin bar status
1334 function syncAdminBarStatus() {
1335 var pluginPageStatus = $('.metasync-integration-status .status-text').text();
1336 var adminBarItem = $('#wp-admin-bar-searchatlas-status .ab-item');
1337 var adminBarContainer = $('#wp-admin-bar-searchatlas-status');
1338 var pluginName = window.MetasyncConfig.pluginName;
1339
1340 if (pluginPageStatus && adminBarItem.length) {
1341 var allClasses = 'searchatlas-synced searchatlas-not-synced searchatlas-warning';
1342
1343 // Helper to update emoji in admin bar
1344 function updateAdminBarEmoji(targetEmoji, targetSvgCode) {
1345 var emojiImg = adminBarItem.find('img.emoji');
1346 if (emojiImg.length > 0) {
1347 emojiImg.attr('alt', targetEmoji);
1348 var currentSrc = emojiImg.attr('src');
1349 var updatedSrc = currentSrc.replace(/1f7e2\.svg|1f534\.svg|1f7e1\.svg/, targetSvgCode + '.svg');
1350 emojiImg.attr('src', updatedSrc);
1351 } else {
1352 var newHtml = adminBarItem.html().replace(/🟢|🔴|🟡/, targetEmoji);
1353 if (!newHtml.includes(targetEmoji) && newHtml.includes(pluginName)) {
1354 newHtml = newHtml.replace(pluginName, pluginName + ' ' + targetEmoji);
1355 }
1356 adminBarItem.html(newHtml);
1357 }
1358 }
1359
1360 if (pluginPageStatus.includes('Synced') && !pluginPageStatus.includes('Not Synced')) {
1361 // Update admin bar to synced (GREEN)
1362 updateAdminBarEmoji('🟢', '1f7e2');
1363 adminBarContainer.removeClass(allClasses).addClass('searchatlas-synced');
1364 var syncTitle = pluginName + ' - Synced (Heartbeat API connectivity verified)';
1365 adminBarContainer.attr('title', syncTitle);
1366 adminBarItem.attr('title', syncTitle);
1367
1368 } else if (pluginPageStatus.includes('Warning')) {
1369 // Update admin bar to warning (YELLOW)
1370 updateAdminBarEmoji('🟡', '1f7e1');
1371 adminBarContainer.removeClass(allClasses).addClass('searchatlas-warning');
1372 var warnTitle = pluginName + ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1373 adminBarContainer.attr('title', warnTitle);
1374 adminBarItem.attr('title', warnTitle);
1375
1376 } else if (pluginPageStatus.includes('Not Synced')) {
1377 // Update admin bar to not synced (RED)
1378 updateAdminBarEmoji('🔴', '1f534');
1379 adminBarContainer.removeClass(allClasses).addClass('searchatlas-not-synced');
1380 var notSyncTitle = pluginName + ' - Not Synced (Heartbeat API not responding or unreachable)';
1381 adminBarContainer.attr('title', notSyncTitle);
1382 adminBarItem.attr('title', notSyncTitle);
1383 }
1384 }
1385 }
1386
1387 // Sync when tabs are switched (for General/Advanced tabs)
1388 $(document).on('click', 'a[href*="tab="]', function() {
1389 setTimeout(syncAdminBarStatus, 200);
1390 });
1391
1392 // Also check every 5 seconds to keep it in sync
1393 setInterval(syncAdminBarStatus, 5000);
1394 });
1395 </script>
1396 <?php endif; ?>
1397 <?php
1398 }
1399
1400 /**
1401 * Add Search Atlas status indicator to WordPress admin bar.
1402 */
1403 public function add_searchatlas_admin_bar_status($wp_admin_bar)
1404 {
1405 if (!Metasync::current_user_has_plugin_access()) {
1406 return;
1407 }
1408
1409 # For backward compatibility, constant takes precedence.
1410 if (defined('METASYNC_SHOW_ADMIN_BAR_STATUS') && !METASYNC_SHOW_ADMIN_BAR_STATUS) {
1411 return;
1412 }
1413
1414 # Check if admin bar status is disabled via setting
1415 $general_settings = Metasync::get_option('general');
1416 if (!is_array($general_settings)) {
1417 $general_settings = [];
1418 }
1419 $show_admin_bar = $general_settings['show_admin_bar_status'] ?? true;
1420 if (!$show_admin_bar) {
1421 return;
1422 }
1423
1424 if (!is_admin() && !apply_filters('metasync_show_admin_bar_status_frontend', false)) {
1425 return;
1426 }
1427
1428 $node = get_transient(self::CACHE_KEY);
1429
1430 if ($node === false) {
1431 $is_synced = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings);
1432
1433 $plugin_name = Metasync::get_effective_plugin_name();
1434
1435 if ($is_synced) {
1436 $otto_uuid = $general_settings['otto_pixel_uuid'] ?? '';
1437 if (!empty($otto_uuid)) {
1438 $status_emoji = '🟢'; // Green circle for synced
1439 $title = $plugin_name . ' - Synced (Heartbeat API connectivity verified)';
1440 $status_class = 'searchatlas-synced';
1441 } else {
1442 $status_emoji = '🟡'; // Yellow circle for warning
1443 $title = $plugin_name . ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.';
1444 $status_class = 'searchatlas-warning';
1445 }
1446 } else {
1447 $status_emoji = '🔴'; // Red circle for not synced
1448 $title = $plugin_name . ' - Not Synced (Heartbeat API not responding or unreachable)';
1449 $status_class = 'searchatlas-not-synced';
1450 }
1451
1452 $admin_bar_title = $plugin_name . ' ' . $status_emoji;
1453
1454 $node = array(
1455 'title' => $admin_bar_title,
1456 'href' => admin_url('admin.php?page=' . Metasync_Admin::$page_slug),
1457 'meta_title' => $title,
1458 'meta_class' => $status_class,
1459 );
1460
1461 set_transient(self::CACHE_KEY, $node, apply_filters('metasync_admin_bar_status_cache_ttl', 5 * MINUTE_IN_SECONDS));
1462 }
1463
1464 $wp_admin_bar->add_node(array(
1465 'id' => 'searchatlas-status',
1466 'title' => $node['title'],
1467 'href' => $node['href'],
1468 'meta' => array(
1469 'title' => $node['meta_title'],
1470 'class' => $node['meta_class'],
1471 ),
1472 ));
1473 }
1474
1475 /**
1476 * Invalidate the admin bar status cache so the next page load recomputes status.
1477 */
1478 public static function invalidate_admin_bar_status_cache()
1479 {
1480 delete_transient(self::CACHE_KEY);
1481 }
1482
1483 // ------------------------------------------------------------------
1484 // Yoast-style 3-column layout helpers
1485 // ------------------------------------------------------------------
1486
1487 /**
1488 * Open the 3-column page layout.
1489 * Call this at the start of every admin page callback instead of
1490 * render_plugin_header() + render_navigation_menu().
1491 * Close with render_layout_close().
1492 *
1493 * @param string $page_title Human-readable page title.
1494 * @param string $current_page Key matching get_available_menu_items() (e.g. 'general').
1495 * @param string $description Optional subtitle shown below the title.
1496 */
1497 public function render_layout_open($page_title = '', $current_page = '', $description = '')
1498 {
1499 $theme = esc_attr(get_option('metasync_theme', 'dark'));
1500 $plugin_name = Metasync::get_effective_plugin_name();
1501 $general = Metasync::get_option('general') ?? [];
1502 $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general);
1503 $logo = $this->resolve_logo_data();
1504
1505 $current_theme = get_option('metasync_theme', 'dark');
1506 $api_key = $general['searchatlas_api_key'] ?? '';
1507 ?>
1508 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo $theme; ?>">
1509
1510 <?php
1511 // Inject whitelabel color palette overrides
1512 $wl_settings = Metasync::get_whitelabel_settings();
1513 $wl_palette = isset($wl_settings['color_palette']) && is_array($wl_settings['color_palette']) ? $wl_settings['color_palette'] : array();
1514 if (!empty($wl_palette)) {
1515 echo '<style id="metasync-whitelabel-palette">';
1516 foreach (array('dark', 'light') as $t) {
1517 if (!empty($wl_palette[$t]) && is_array($wl_palette[$t])) {
1518 $selector = ($t === 'dark') ? ':root, [data-theme="dark"]' : '[data-theme="light"]';
1519 $vars = '';
1520 foreach ($wl_palette[$t] as $var_name => $color) {
1521 // Skip gradient partial keys — handled below
1522 if (strpos($var_name, 'gradient-') !== false) continue;
1523 if (preg_match('/^dashboard-[a-z-]+$/', $var_name) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $color)) {
1524 $vars .= '--' . esc_attr($var_name) . ':' . esc_attr($color) . ';';
1525 }
1526 }
1527 // Build gradient overrides from paired from/to colors
1528 $gradient_pairs = array(
1529 'dashboard-gradient-primary' => array('dashboard-gradient-primary-from', 'dashboard-gradient-primary-to'),
1530 'dashboard-gradient-accent' => array('dashboard-gradient-accent-from', 'dashboard-gradient-accent-to'),
1531 );
1532 foreach ($gradient_pairs as $grad_var => $pair) {
1533 $from = isset($wl_palette[$t][$pair[0]]) ? $wl_palette[$t][$pair[0]] : '';
1534 $to = isset($wl_palette[$t][$pair[1]]) ? $wl_palette[$t][$pair[1]] : '';
1535 if ($from && $to && preg_match('/^#[0-9a-fA-F]{3,8}$/', $from) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $to)) {
1536 $vars .= '--' . esc_attr($grad_var) . ':linear-gradient(135deg,' . esc_attr($from) . ' 0%,' . esc_attr($to) . ' 100%);';
1537 }
1538 }
1539 if ($vars) {
1540 echo $selector . '{' . $vars . '}';
1541 }
1542 }
1543 }
1544 echo '</style>';
1545 }
1546 ?>
1547
1548 <!-- Compact top header -->
1549 <div class="metasync-header-compact">
1550 <div style="display:flex;align-items:center;gap:10px;">
1551 <?php if ($logo['show_logo'] && $logo['use_dual']): ?>
1552 <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;">
1553 <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;">
1554 <?php elseif ($logo['show_logo'] && $logo['url']): ?>
1555 <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;">
1556 <?php else: ?>
1557 <strong style="font-size:15px;color:var(--dashboard-text-primary);"><?php echo esc_html($plugin_name); ?></strong>
1558 <?php endif; ?>
1559 </div>
1560 <div class="metasync-header-compact-right">
1561 <div class="metasync-status <?php echo !empty($api_key) ? 'connected' : 'disconnected'; ?>">
1562 <span class="status-dot"></span>
1563 <span class="status-text"><?php echo !empty($api_key) ? 'Connected' : 'Not Connected'; ?></span>
1564 </div>
1565 <button type="button" class="metasync-theme-toggle" onclick="toggleMetasyncTheme()" title="Toggle theme">
1566 <span class="theme-icon-light">&#9728;</span>
1567 <span class="theme-icon-dark">&#9790;</span>
1568 </button>
1569 </div>
1570 </div>
1571
1572 <!-- 3-column layout -->
1573 <div class="metasync-layout">
1574
1575 <!-- Left sidenav -->
1576 <aside class="metasync-layout-nav">
1577 <?php $this->render_sidenav($current_page); ?>
1578 </aside>
1579
1580 <!-- Main content -->
1581 <main class="metasync-layout-main">
1582 <?php if ($page_title || $description): ?>
1583 <div class="metasync-page-header">
1584 <?php if ($page_title): ?>
1585 <h1><?php echo esc_html($page_title); ?></h1>
1586 <?php endif; ?>
1587 <?php if ($description): ?>
1588 <p><?php echo esc_html($description); ?></p>
1589 <?php endif; ?>
1590 </div>
1591 <?php endif; ?>
1592 <?php
1593 // Note: render_layout_close() closes </main>, renders promo sidebar, closes </div>.metasync-layout and </div>.wrap
1594 }
1595
1596 /**
1597 * Close the 3-column layout opened by render_layout_open().
1598 *
1599 * @param bool $show_promo Whether to render the right promo sidebar. Default true.
1600 * Pass false for full-width pages (e.g. dashboard iframe).
1601 */
1602 public function render_layout_close($show_promo = true)
1603 {
1604 ?>
1605 </main><!-- /.metasync-layout-main -->
1606
1607 <?php if ($show_promo):
1608 $general = Metasync::get_option('general') ?? [];
1609 $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general);
1610 ?>
1611 <!-- Right promo sidebar -->
1612 <aside class="metasync-layout-promo">
1613 <?php $this->render_promo_sidebar($is_connected); ?>
1614 </aside>
1615 <?php endif; ?>
1616
1617 </div><!-- /.metasync-layout -->
1618 </div><!-- /.metasync-dashboard-wrap -->
1619 <?php
1620 }
1621
1622 /**
1623 * Render the left sticky sidenav with grouped items.
1624 *
1625 * @param string $current_page Active page key.
1626 */
1627 public function render_sidenav($current_page = '')
1628 {
1629 $page_slug = Metasync_Admin::$page_slug;
1630 $menu_items = $this->get_available_menu_items();
1631
1632 // Dashicons names (without 'dashicons-' prefix) — monochrome, color set by CSS
1633 $icons = [
1634 'dashboard' => 'dashboard',
1635 'seo_controls' => 'search',
1636 'monitor_404' => 'warning',
1637 'redirections' => 'undo',
1638 'xml_sitemap' => 'networking',
1639 'robots_txt' => 'shield',
1640 'site_verification'=> 'yes-alt',
1641 'instant_index' => 'performance',
1642 'google_console' => 'chart-area',
1643 'bing_console' => 'chart-bar',
1644 'import_seo' => 'download',
1645 'general' => 'admin-settings',
1646 'schema_markup' => 'tag',
1647 'local_business' => 'building',
1648 'breadcrumbs' => 'menu',
1649 'code_snippets' => 'editor-code',
1650 'code_minification'=> 'media-code',
1651 'custom_pages' => 'admin-page',
1652 'optimal_settings' => 'superhero-alt',
1653 'compatibility' => 'admin-tools',
1654 'sync_log' => 'list-view',
1655 'bot_statistics' => 'visibility',
1656 'report_issue' => 'sos',
1657 'media_optimization'=> 'images-alt2',
1658 'seo_health' => 'heart'
1659 ];
1660
1661 $seo_items = [];
1662 $plugin_items = [];
1663 foreach ($menu_items as $key => $item) {
1664 if (($item['group'] ?? 'plugin') === 'seo') {
1665 $seo_items[$key] = $item;
1666 } else {
1667 $plugin_items[$key] = $item;
1668 }
1669 }
1670 ?>
1671 <nav class="metasync-sidenav">
1672
1673 <!-- SEO Features group -->
1674 <div class="metasync-sidenav-group">
1675 <div class="metasync-sidenav-group-title">SEO Features</div>
1676 <ul>
1677 <?php foreach ($seo_items as $key => $item):
1678 $is_active = ($current_page === $key);
1679 $icon = $icons[$key] ?? 'admin-generic';
1680 $url = esc_url(admin_url('admin.php?page=' . $page_slug . $item['slug_suffix']));
1681 ?>
1682 <li class="<?php echo $is_active ? 'metasync-sidenav-active' : ''; ?>">
1683 <a href="<?php echo $url; ?>">
1684 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
1685 <?php echo esc_html($item['title']); ?>
1686 </a>
1687 </li>
1688 <?php endforeach; ?>
1689 </ul>
1690 </div>
1691
1692 <!-- Plugin group -->
1693 <div class="metasync-sidenav-group">
1694 <div class="metasync-sidenav-group-title">Plugin</div>
1695 <ul>
1696 <?php foreach ($plugin_items as $key => $item):
1697 if ($key === 'report_issue') continue;
1698 $is_active = ($current_page === $key);
1699 $icon = $icons[$key] ?? 'admin-generic';
1700 $url = esc_url(admin_url('admin.php?page=' . $page_slug . $item['slug_suffix']));
1701 ?>
1702 <li class="<?php echo $is_active ? 'metasync-sidenav-active' : ''; ?>">
1703 <a href="<?php echo $url; ?>">
1704 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span></span>
1705 <?php echo esc_html($item['title']); ?>
1706 </a>
1707 </li>
1708 <?php if ($key === 'general'): ?>
1709 <li class="<?php echo $current_page === 'advanced_settings' ? 'metasync-sidenav-active' : ''; ?>" style="padding-left: 8px;">
1710 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . '&tab=advanced')); ?>">
1711 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-admin-generic"></span></span>
1712 Advanced Settings
1713 </a>
1714 </li>
1715 <li class="<?php echo $current_page === 'whitelabel' ? 'metasync-sidenav-active' : ''; ?>" style="padding-left: 8px;">
1716 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . '&tab=whitelabel')); ?>">
1717 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-tag"></span></span>
1718 Whitelabel
1719 </a>
1720 </li>
1721 <?php endif; ?>
1722 <?php endforeach; ?>
1723 </ul>
1724 </div>
1725
1726 <!-- Report Issue at the bottom -->
1727 <?php if (isset($menu_items['report_issue'])): ?>
1728 <div class="metasync-sidenav-group">
1729 <ul>
1730 <li class="<?php echo $current_page === 'report_issue' ? 'metasync-sidenav-active' : ''; ?>">
1731 <a href="<?php echo esc_url(admin_url('admin.php?page=' . $page_slug . $menu_items['report_issue']['slug_suffix'])); ?>">
1732 <span class="metasync-sidenav-icon"><span class="dashicons dashicons-sos"></span></span>
1733 Report Issue
1734 </a>
1735 </li>
1736 </ul>
1737 </div>
1738 <?php endif; ?>
1739
1740 </nav>
1741 <?php
1742 }
1743
1744 /**
1745 * Render the right promotional sidebar.
1746 *
1747 * @param bool $is_connected Whether the plugin is authenticated.
1748 */
1749 public function render_promo_sidebar($is_connected = false)
1750 {
1751 $plugin_name = Metasync::get_effective_plugin_name();
1752 $otto_name = Metasync::get_whitelabel_otto_name();
1753 $settings_url = esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug));
1754 $homepage = Metasync::HOMEPAGE_DOMAIN;
1755 $is_default_brand = ( $plugin_name === 'Search Atlas' );
1756 $whitelabel = Metasync::get_whitelabel_settings();
1757 $custom_links = isset($whitelabel['quick_links']) && is_array($whitelabel['quick_links'])
1758 ? array_filter($whitelabel['quick_links'], function($l) { return !empty($l['url']); })
1759 : [];
1760 ?>
1761
1762 <?php if (!$is_connected): ?>
1763 <!-- Connect CTA card -->
1764 <div class="metasync-promo-card metasync-promo-card--connect">
1765 <div class="metasync-promo-card-header">
1766 <div class="metasync-promo-card-icon">
1767 <span class="dashicons dashicons-admin-links"></span>
1768 </div>
1769 <div>
1770 <h3>Connect <?php echo esc_html($plugin_name); ?></h3>
1771 </div>
1772 </div>
1773 <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>
1774 <ul class="metasync-promo-benefits">
1775 <li><span class="promo-check">&#10003;</span> <?php echo esc_html($otto_name); ?> hands-free on-page SEO</li>
1776 <li><span class="promo-check">&#10003;</span> Real-time keyword tracking</li>
1777 <li><span class="promo-check">&#10003;</span> Automated schema markup</li>
1778 <li><span class="promo-check">&#10003;</span> Instant Google indexing</li>
1779 </ul>
1780 <a href="<?php echo $settings_url; ?>" class="metasync-promo-btn metasync-promo-btn--primary">
1781 Connect Now
1782 </a>
1783 </div>
1784 <?php else: ?>
1785 <!-- Connected feature highlights -->
1786 <div class="metasync-promo-card metasync-promo-card--accent">
1787 <div class="metasync-promo-card-header">
1788 <div class="metasync-promo-card-icon">
1789 <span class="dashicons dashicons-performance"></span>
1790 </div>
1791 <div>
1792 <h3><?php echo esc_html($otto_name); ?> Active</h3>
1793 </div>
1794 </div>
1795 <p class="metasync-promo-tagline">Your site is connected and <?php echo esc_html($otto_name); ?> is optimizing pages automatically.</p>
1796 <ul class="metasync-promo-benefits">
1797 <li><span class="promo-check">&#10003;</span> Schema markup auto-applied</li>
1798 <li><span class="promo-check">&#10003;</span> Meta titles &amp; descriptions optimized</li>
1799 <li><span class="promo-check">&#10003;</span> Internal linking suggestions active</li>
1800 </ul>
1801 <?php if ($is_default_brand): ?>
1802 <a href="<?php echo esc_url($homepage); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1803 View <?php echo esc_html($plugin_name); ?> Dashboard
1804 </a>
1805 <?php elseif (!empty($whitelabel['domain'])): ?>
1806 <a href="<?php echo esc_url($whitelabel['domain']); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1807 View <?php echo esc_html($plugin_name); ?> Dashboard
1808 </a>
1809 <?php endif; ?>
1810 </div>
1811 <?php endif; ?>
1812
1813 <?php
1814 // Quick Links: show default links for default brand, custom links if whitelabeled + provided, hide if whitelabeled + none
1815 $show_quick_links = $is_default_brand || !empty($custom_links);
1816 if ($show_quick_links):
1817 ?>
1818 <!-- Quick links card -->
1819 <div class="metasync-promo-card">
1820 <h3 style="margin:0 0 12px;font-size:13px;font-weight:700;color:var(--dashboard-text-primary);">Quick Links</h3>
1821 <ul class="metasync-promo-links">
1822 <?php if ($is_default_brand): ?>
1823 <li><a href="<?php echo esc_url($homepage . '/blog/'); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-rss"></span> SEO Blog</a></li>
1824 <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>
1825 <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>
1826 <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>
1827 <?php else: ?>
1828 <?php foreach ($custom_links as $link): ?>
1829 <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>
1830 <?php endforeach; ?>
1831 <?php endif; ?>
1832 </ul>
1833 </div>
1834 <?php endif; ?>
1835
1836 <?php
1837 }
1838 }
1839