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

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