get_available_menu_items(); foreach (self::WP_MENU_KEEP_LIST as $key) { if (isset($available[$key])) { return array($admin, $available[$key]['callback']); } } // No keep-list item is reachable — keep Settings as the last resort // so the menu never leads to a blank page. Settings itself handles // its own access-control rendering. return array($admin, 'create_admin_settings_page'); } /** * The ordered keep-list of menu item keys that always retain a * WordPress admin sidebar row (in short mode). * * @return string[] */ public static function get_wp_menu_keep_list() { return self::WP_MENU_KEEP_LIST; } /** * Whether a given menu item key retains a WordPress admin sidebar row. * * Only keep-list items retain a WordPress sidebar row. All other pages * remain registered and available from the in-page navigation. * * @param string $item_key Menu item key from get_available_menu_items(). * @return bool */ public static function item_keeps_wp_sidebar_row($item_key) { return in_array($item_key, self::WP_MENU_KEEP_LIST, true); } /** * Get singleton instance. * * @return self */ public static function instance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } private function __construct() {} /** * Whether the Dashboard is hidden by the "Hide Dashboard" general setting * (hide_dashboard_framework). * * When enabled this removes the Dashboard from the WordPress admin submenu * and from the in-plugin SEO feature navigation, matching the setting's * description "Hide the main dashboard from the WordPress admin menu". * * @return bool */ public static function is_dashboard_hidden_by_framework() { $general_options = Metasync::get_option('general') ?? []; return filter_var($general_options['hide_dashboard_framework'] ?? false, FILTER_VALIDATE_BOOLEAN); } // ------------------------------------------------------------------ // Logo resolution helper // ------------------------------------------------------------------ /** * Resolve which logo(s) to display based on whitelabel settings. * * @return array{show_logo: bool, use_dual: bool, url: string, light_url: string, dark_url: string, is_default: bool} */ private function resolve_logo_data() { $wl_settings = Metasync::get_whitelabel_settings(); $is_whitelabel = !empty($wl_settings['is_whitelabel']); $light_raw = Metasync::get_whitelabel_logo_light(); $dark_raw = Metasync::get_whitelabel_logo_dark(); $has_light = !empty($light_raw) && filter_var($light_raw, FILTER_VALIDATE_URL); $has_dark = !empty($dark_raw) && filter_var($dark_raw, FILTER_VALIDATE_URL); $use_dual = ($has_light && $has_dark && $light_raw !== $dark_raw); $data = [ 'show_logo' => false, 'use_dual' => false, 'url' => '', 'light_url' => '', 'dark_url' => '', 'is_default' => false, ]; if ($use_dual) { $data['show_logo'] = true; $data['use_dual'] = true; $data['light_url'] = $light_raw; $data['dark_url'] = $dark_raw; } else { $single = $has_light ? $light_raw : ($has_dark ? $dark_raw : null); if ($single) { $data['show_logo'] = true; $data['url'] = $single; } elseif (!$is_whitelabel) { $data['show_logo'] = true; $data['url'] = plugin_dir_url(dirname(__FILE__)) . 'assets/images/searchatlas-logo.svg'; $data['is_default'] = true; } } return $data; } // ------------------------------------------------------------------ // Header + Nav rendering // ------------------------------------------------------------------ /** * Render the standard header followed by the navigation tabs. */ public function render_standard_header_nav($page_title = null, $current_page = null) { $this->render_static_header($page_title); $this->render_static_navigation($current_page); } /** * Render the page header (logo, connection badge, theme toggle). */ public function render_static_header($page_title = null) { $effective_plugin_name = Metasync::get_effective_plugin_name(); $display_title = $page_title ?: $effective_plugin_name; $logo = $this->resolve_logo_data(); $current_theme = get_option('metasync_theme', 'dark'); $general_settings = Metasync::get_option('general'); // derive the badge from confirmed heartbeat health, not from // mere API-key presence (which stays set after Search Atlas revokes it). $badge = Metasync_Heartbeat_Manager::instance()->get_connection_badge($general_settings); ?>
Logo
'dashboard', 'seo_controls' => 'search', 'instant_index' => 'performance', 'google_console' => 'chart-area', 'compatibility' => 'admin-tools', 'sync_log' => 'list-view', 'redirections' => 'undo', 'robots_txt' => 'shield', 'xml_sitemap' => 'networking', 'custom_pages' => 'admin-page', 'report_issue' => 'sos', 'general' => 'admin-settings', ]; if (empty($whitelabel_settings['hide_dashboard']) && !self::is_dashboard_hidden_by_framework()) { $menu_items['dashboard'] = ['title' => 'Dashboard', 'slug_suffix' => '-dashboard']; } if (empty($whitelabel_settings['hide_indexation_control'])) { $menu_items['seo_controls'] = ['title' => 'Indexation Control', 'slug_suffix' => '-seo-controls']; } if ($general_options['enable_googleinstantindex'] ?? false) { $menu_items['instant_index'] = ['title' => 'Instant Indexing', 'slug_suffix' => '-instant-index']; } if ($general_options['enable_google_console'] ?? false) { $menu_items['google_console'] = ['title' => 'Google Console', 'slug_suffix' => '-google-console']; } if (empty($whitelabel_settings['hide_compatibility'])) { $menu_items['compatibility'] = ['title' => 'Compatibility', 'slug_suffix' => '-compatibility']; } if (empty($whitelabel_settings['hide_sync_log'])) { $menu_items['sync_log'] = ['title' => 'Changes Log', 'slug_suffix' => '-sync-log']; } if (empty($whitelabel_settings['hide_redirections'])) { $menu_items['redirections'] = ['title' => 'Redirections', 'slug_suffix' => '-redirections']; } if (empty($whitelabel_settings['hide_robots'])) { $menu_items['robots_txt'] = ['title' => 'Robots.txt', 'slug_suffix' => '-robots-txt']; } $menu_items['xml_sitemap'] = ['title' => 'XML Sitemap', 'slug_suffix' => '-xml-sitemap']; ?>
$menu_item): $is_active = ($current_page === $key); $icon = $menu_icons[$key]; $page_url = '?page=' . $page_slug . $menu_item['slug_suffix']; ?>
Custom Pages Report Issue
is_heartbeat_connected($general_options); $menu_items = []; // === SEO FEATURES GROUP === // Dashboard (check access control + "Hide Dashboard" general setting) if (Metasync_Access_Control::user_can_access('hide_dashboard') && !self::is_dashboard_hidden_by_framework()) { $menu_items['dashboard'] = [ 'title' => 'Dashboard', 'slug_suffix' => '-dashboard', 'callback' => 'create_admin_dashboard_iframe', 'internal_nav' => 'Dashboard', 'group' => 'seo' ]; } // Indexation Control (check access control) if (Metasync_Access_Control::user_can_access('hide_indexation_control')) { $menu_items['seo_controls'] = [ 'title' => 'Indexation', 'slug_suffix' => '-seo-controls', 'callback' => 'create_admin_seo_controls_page', 'internal_nav' => 'Indexation Control', 'group' => 'seo' ]; } // Instant Indexing - setting now stored in seo_controls (moved from Settings to Indexation Control) $seo_controls = Metasync::get_option('seo_controls'); if ($seo_controls['enable_googleinstantindex'] ?? false) { $menu_items['instant_index'] = [ 'title' => 'Instant Indexing', 'slug_suffix' => '-instant-index', 'callback' => 'create_admin_google_instant_index_page', 'internal_nav' => 'Instant Indexing', 'group' => 'seo' ]; } if ($general_options['enable_google_console'] ?? false) { $menu_items['google_console'] = [ 'title' => 'Google Console', 'slug_suffix' => '-google-console', 'callback' => 'create_admin_google_console_page', 'internal_nav' => 'Google Console', 'group' => 'seo' ]; } if ($general_options['enable_bing_console'] ?? false) { $menu_items['bing_console'] = [ 'title' => 'Bing Console', 'slug_suffix' => '-bing-console', 'callback' => 'create_admin_bing_console_page', 'internal_nav' => 'Bing Console', 'group' => 'seo' ]; } // Redirections page (check access control) if (Metasync_Access_Control::user_can_access('hide_redirections')) { $menu_items['redirections'] = [ 'title' => 'Redirections', 'slug_suffix' => '-redirections', 'callback' => 'create_admin_redirections_page', 'internal_nav' => 'Redirections', 'group' => 'seo' ]; } // Robots.txt page (check access control) if (Metasync_Access_Control::user_can_access('hide_robots')) { $menu_items['robots_txt'] = [ 'title' => 'Robots.txt', 'slug_suffix' => '-robots-txt', 'callback' => 'create_admin_robots_txt_page', 'internal_nav' => 'Robots.txt', 'group' => 'seo' ]; } // XML Sitemap page (check access control) if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) { $menu_items['xml_sitemap'] = [ 'title' => 'XML Sitemap', 'slug_suffix' => '-xml-sitemap', 'callback' => 'create_admin_xml_sitemap_page', 'internal_nav' => 'XML Sitemap', 'group' => 'seo' ]; } // Schema Markup $menu_items['schema_markup'] = [ 'title' => 'Schema Markup', 'slug_suffix' => '-schema-markup', 'callback' => 'create_admin_schema_markup_page', 'internal_nav' => 'Schema Markup', 'group' => 'seo' ]; // 404 Monitor (always available — SEO monitoring tool) $menu_items['monitor_404'] = [ 'title' => '404 Monitor', 'slug_suffix' => '-404-monitor', 'callback' => 'create_admin_404_monitor_page', 'internal_nav' => '404 Monitor', 'group' => 'seo' ]; // Site Verification $menu_items['site_verification'] = [ 'title' => 'Site Verification', 'slug_suffix' => '-search-engine-verify', 'callback' => 'create_admin_search_engine_verification_page', 'internal_nav' => 'Site Verification', 'group' => 'seo' ]; // SEO Health dashboard (always available) $menu_items['seo_health'] = [ 'title' => 'SEO Health', 'slug_suffix' => '-seo-health', 'callback' => 'create_admin_seo_health_page', 'internal_nav' => 'SEO Health', 'group' => 'seo' ]; // Import SEO Data page (check access control) if (Metasync_Access_Control::user_can_access('hide_import_seo')) { $menu_items['import_seo'] = [ 'title' => 'Import SEO Data', 'slug_suffix' => '-import-external', 'callback' => 'render_import_external_data_page', 'internal_nav' => 'Import SEO Data', 'group' => 'seo' ]; } // === PLUGIN GROUP === // Settings (check access control) if (Metasync_Access_Control::user_can_access('hide_settings')) { $menu_items['general'] = [ 'title' => 'Settings', 'slug_suffix' => '', 'callback' => 'create_admin_settings_page', 'internal_nav' => 'General Settings', 'group' => 'plugin' ]; } // Local Business $menu_items['local_business'] = [ 'title' => 'Local Business', 'slug_suffix' => '-local-business', 'callback' => 'create_admin_local_business_page', 'internal_nav' => 'Local Business', 'group' => 'seo' ]; // Breadcrumbs $menu_items['breadcrumbs'] = [ 'title' => 'Breadcrumbs', 'slug_suffix' => '-breadcrumbs', 'callback' => 'create_admin_breadcrumbs_page', 'internal_nav' => 'Breadcrumbs', 'group' => 'seo' ]; // Code Snippets $menu_items['code_snippets'] = [ 'title' => 'Code Snippets', 'slug_suffix' => '-code-snippets', 'callback' => 'create_admin_code_snippets_page', 'internal_nav' => 'Code Snippets', 'group' => 'plugin' ]; // Code Minification $menu_items['code_minification'] = [ 'title' => 'Code Minification', 'slug_suffix' => '-code-minification', 'callback' => 'create_admin_code_minification_page', 'internal_nav' => 'Code Minification', 'group' => 'plugin' ]; // Media Optimization $menu_items['media_optimization'] = [ 'title' => 'Media Optimization', 'slug_suffix' => '-media-optimization', 'callback' => 'create_admin_media_optimization_page', 'internal_nav' => 'Media Optimization', 'group' => 'plugin' ]; // Custom HTML Pages (check access control) if (Metasync_Access_Control::user_can_access('hide_custom_pages')) { $menu_items['custom_pages'] = [ 'title' => 'Custom Pages', 'slug_suffix' => '-custom-pages', 'callback' => 'create_admin_custom_pages_page', 'internal_nav' => 'Custom HTML Pages', 'group' => 'plugin' ]; } // Compatibility page (check access control) if (Metasync_Access_Control::user_can_access('hide_compatibility')) { $menu_items['compatibility'] = [ 'title' => 'Compatibility', 'slug_suffix' => '-compatibility', 'callback' => 'create_admin_compatibility_page', 'internal_nav' => 'Compatibility', 'group' => 'plugin' ]; } // Sync Log page (check access control) if (Metasync_Access_Control::user_can_access('hide_sync_log')) { $menu_items['sync_log'] = [ 'title' => 'Changes Log', 'slug_suffix' => '-sync-log', 'callback' => 'create_admin_sync_log_page', 'internal_nav' => 'Changes Log', 'group' => 'plugin' ]; } // Bot Statistics $menu_items['bot_statistics'] = [ 'title' => 'Bot Statistics', 'slug_suffix' => '-bot-statistics', 'callback' => 'create_admin_bot_statistics_page', 'internal_nav' => 'Bot Statistics', 'group' => 'plugin' ]; // Report Issue page (check access control) if (Metasync_Access_Control::user_can_access('hide_report_issue')) { $menu_items['report_issue'] = [ 'title' => 'Report Issue', 'slug_suffix' => '-report-issue', 'callback' => 'create_admin_report_issue_page', 'internal_nav' => 'Report Issue', 'group' => 'plugin' ]; } return $menu_items; } /** * Register all WordPress admin menu / submenu pages. * * @param Metasync_Admin $admin The admin instance whose callbacks WordPress will invoke. */ public function add_plugin_settings_page($admin) { if (!Metasync::current_user_has_plugin_access()) { return; } $data = Metasync::get_option('general'); $plugin_name = Metasync::get_effective_plugin_name(); $menu_name = $plugin_name; $menu_title = $plugin_name; // Sanitize so a URL-shaped legacy value still yields a valid WP menu slug $menu_slug = !isset($data['white_label_plugin_menu_slug']) || $data['white_label_plugin_menu_slug'] == "" ? Metasync_Admin::$page_slug : sanitize_title($data['white_label_plugin_menu_slug']); $menu_slug = $menu_slug === '' ? Metasync_Admin::$page_slug : $menu_slug; $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']; // Use 'read' capability since actual access is controlled by current_user_has_plugin_access() check above $menu_capability = 'read'; // Separator just before the plugin entry, after all other plugins (Yoast etc. ~99) add_action('admin_menu', function() { global $menu; $menu['100'] = array('', 'read', 'separator-metasync-before', '', 'wp-menu-separator'); }, 5); // Main menu page at position 100.1 — bottom of the menu after all standard items. // The top-level callback is the floor: when Settings is reachable it renders // Settings; when Settings is hidden it falls back to the first reachable // keep-list page so the menu never leads to a blank page. $top_level_callback = self::resolve_top_level_callback($admin); add_menu_page( $menu_name, $menu_title, $menu_capability, $menu_slug, $top_level_callback, $menu_icon, '100.1' ); // Check connection status for submenu availability $general_options = Metasync::get_option('general'); $has_api_key = !empty($general_options['searchatlas_api_key']); $has_uuid = !empty($general_options['otto_pixel_uuid']); $is_fully_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_options); $seo_controls = Metasync::get_option('seo_controls'); // ── Connect slug ────────────────────────────────────────────────── $connect_slug = $menu_slug . '-connect'; // Local helper: register a submenu page under the plugin parent slug // when the item keeps its WordPress admin sidebar row, or under an // empty parent slug (hidden from the sidebar but still reachable) // otherwise. The Settings page (general) is registered against the // bare $menu_slug so it doubles as the parent menu callback. $register_submenu = function($item_key, $page_title, $menu_title, $capability, $menu_page_slug, $callback) use ($menu_slug) { $parent = self::item_keeps_wp_sidebar_row($item_key) ? $menu_slug : ''; add_submenu_page($parent, $page_title, $menu_title, $capability, $menu_page_slug, $callback); }; // Dashboard (check access control + "Hide Dashboard" general setting) if (Metasync_Access_Control::user_can_access('hide_dashboard') && !self::is_dashboard_hidden_by_framework()) { $register_submenu('dashboard', 'Dashboard', 'Dashboard', $menu_capability, $menu_slug . '-dashboard', array($admin, 'create_admin_dashboard_iframe')); } // Indexation Control if (Metasync_Access_Control::user_can_access('hide_indexation_control')) { $register_submenu('seo_controls', 'Indexation Control', 'Indexation Control', $menu_capability, $menu_slug . '-seo-controls', array($admin, 'create_admin_seo_controls_page')); } // 404 Monitor $register_submenu('monitor_404', '404 Monitor', '404 Monitor', $menu_capability, $menu_slug . '-404-monitor', array($admin, 'create_admin_404_monitor_page')); // Redirections if (Metasync_Access_Control::user_can_access('hide_redirections')) { $register_submenu('redirections', 'Redirections', 'Redirections', $menu_capability, $menu_slug . '-redirections', array($admin, 'create_admin_redirections_page')); } // XML Sitemap if (Metasync_Access_Control::user_can_access('hide_xml_sitemap')) { $register_submenu('xml_sitemap', 'XML Sitemap', 'XML Sitemap', $menu_capability, $menu_slug . '-xml-sitemap', array($admin, 'create_admin_xml_sitemap_page')); } // Robots.txt if (Metasync_Access_Control::user_can_access('hide_robots')) { $register_submenu('robots_txt', 'Robots.txt', 'Robots.txt', $menu_capability, $menu_slug . '-robots-txt', array($admin, 'create_admin_robots_txt_page')); } // Site Verification (in-page sidebar only in short mode) $register_submenu('site_verification', 'Site Verification', 'Site Verification', $menu_capability, $menu_slug . '-search-engine-verify', array($admin, 'create_admin_search_engine_verification_page')); // Instant Indexing (conditional) if ($seo_controls['enable_googleinstantindex'] ?? false) { $register_submenu('instant_index', 'Instant Indexing', 'Instant Indexing', $menu_capability, $menu_slug . '-instant-index', array($admin, 'create_admin_google_instant_index_page')); } // Google Console (conditional) if ($general_options['enable_google_console'] ?? false) { $register_submenu('google_console', 'Google Console', 'Google Console', $menu_capability, $menu_slug . '-google-console', array($admin, 'create_admin_google_console_page')); } // Bing Console (conditional) if ($seo_controls['enable_binginstantindex'] ?? false) { $register_submenu('bing_console', 'Bing Console', 'Bing Console', $menu_capability, $menu_slug . '-bing-console', array($admin, 'create_admin_bing_console_page')); } // Schema Markup $register_submenu('schema_markup', 'Schema Markup', 'Schema Markup', $menu_capability, $menu_slug . '-schema-markup', array($admin, 'create_admin_schema_markup_page')); // Import SEO Data if (Metasync_Access_Control::user_can_access('hide_import_seo')) { $register_submenu('import_seo', 'Import SEO Data', 'Import SEO Data', $menu_capability, $menu_slug . '-import-external', array($admin, 'render_import_external_data_page')); } // Settings — registered against the bare $menu_slug (parent menu floor). // It always keeps its sidebar row so the plugin always has at least // one working entry point (see is_settings_reachable()). if (Metasync_Access_Control::user_can_access('hide_settings')) { $register_submenu('general', 'Settings', 'Settings', $menu_capability, $menu_slug, array($admin, 'create_admin_settings_page')); } // Local Business (in-page sidebar only in short mode) $register_submenu('local_business', 'Local Business', 'Local Business', $menu_capability, $menu_slug . '-local-business', array($admin, 'create_admin_local_business_page')); // Breadcrumbs (in-page sidebar only in short mode) $register_submenu('breadcrumbs', 'Breadcrumbs', 'Breadcrumbs', $menu_capability, $menu_slug . '-breadcrumbs', array($admin, 'create_admin_breadcrumbs_page')); // Code Snippets (in-page sidebar only in short mode) $register_submenu('code_snippets', 'Code Snippets', 'Code Snippets', $menu_capability, $menu_slug . '-code-snippets', array($admin, 'create_admin_code_snippets_page')); // Code Minification (in-page sidebar only in short mode) $register_submenu('code_minification', 'Code Minification', 'Code Minification', $menu_capability, $menu_slug . '-code-minification', array($admin, 'create_admin_code_minification_page')); // Media Optimization (in-page sidebar only in short mode) $register_submenu('media_optimization', 'Media Optimization', 'Media Optimization', $menu_capability, $menu_slug . '-media-optimization', array($admin, 'create_admin_media_optimization_page')); // Custom Pages if (Metasync_Access_Control::user_can_access('hide_custom_pages')) { $register_submenu('custom_pages', 'Custom Pages', 'Custom Pages', $menu_capability, $menu_slug . '-custom-pages', array($admin, 'create_admin_custom_pages_page')); } // Bot Statistics (in-page sidebar only in short mode) $register_submenu('bot_statistics', 'Bot Statistics', 'Bot Statistics', $menu_capability, $menu_slug . '-bot-statistics', array($admin, 'create_admin_bot_statistics_page')); // SEO Health dashboard $register_submenu('seo_health', 'SEO Health', 'SEO Health', $menu_capability, $menu_slug . '-seo-health', array($admin, 'create_admin_seo_health_page')); // Compatibility if (Metasync_Access_Control::user_can_access('hide_compatibility')) { $register_submenu('compatibility', 'Compatibility', 'Compatibility', $menu_capability, $menu_slug . '-compatibility', array($admin, 'create_admin_compatibility_page')); } // Changes Log if (Metasync_Access_Control::user_can_access('hide_sync_log')) { $register_submenu('sync_log', 'Changes Log', 'Changes Log', $menu_capability, $menu_slug . '-sync-log', array($admin, 'create_admin_sync_log_page')); } // Report Issue if (Metasync_Access_Control::user_can_access('hide_report_issue')) { $register_submenu('report_issue', 'Report Issue', 'Report Issue', $menu_capability, $menu_slug . '-report-issue', array($admin, 'create_admin_report_issue_page')); } // ── Connect CTA (shown when not authenticated) ──────────────────── // Kept as a sidebar row so an unconnected site always has a visible // entry point alongside the top-level menu item. if (!$is_fully_connected) { $connect_label = sprintf('Connect to %s', Metasync::get_effective_plugin_name()); add_submenu_page($menu_slug, $connect_label, $connect_label, $menu_capability, $connect_slug, array($admin, 'create_admin_settings_page')); } // ── Hidden pages (no sidebar entry needed) ──────────────────────── add_submenu_page('', 'Setup Wizard', 'Setup Wizard', $menu_capability, $menu_slug . '-setup-wizard', array($admin->setup_wizard, 'render_wizard_page')); // Rename auto-generated first submenu from plugin name to "Settings" and reorder add_action('admin_menu', function() use ($menu_slug) { global $submenu; if (!isset($submenu[$menu_slug])) { return; } // Remove the auto-duplicate of the main menu item (same slug as parent) foreach ($submenu[$menu_slug] as $key => $item) { if ($item[2] === $menu_slug) { unset($submenu[$menu_slug][$key]); break; } } }, 999); } /** * Render the grouped navigation menu with Dashboard + SEO/Plugin dropdowns. */ public function render_navigation_menu($current_page = null) { $page_slug = Metasync_Admin::$page_slug; $available_menu_items = $this->get_available_menu_items(); $menu_icons = [ 'general' => 'admin-settings', 'dashboard' => 'dashboard', 'compatibility' => 'admin-tools', 'sync_log' => 'list-view', 'seo_controls' => 'search', 'instant_index' => 'performance', 'google_console' => 'chart-area', 'bing_console' => 'chart-bar', 'redirections' => 'undo', 'robots_txt' => 'shield', 'xml_sitemap' => 'networking', 'schema_markup' => 'tag', 'site_verification'=> 'yes-alt', 'import_seo' => 'download', 'custom_pages' => 'admin-page', 'code_minification'=> 'media-code', 'bot_statistics' => 'visibility', 'breadcrumbs' => 'menu', 'monitor_404' => 'warning', 'local_business' => 'building', 'code_snippets' => 'editor-code', 'report_issue' => 'sos', 'error_log' => 'warning', 'seo_health' => 'heart' ]; $seo_items = []; $plugin_items = []; foreach ($available_menu_items as $key => $menu_item) { $group = $menu_item['group'] ?? 'plugin'; if ($group === 'seo') { $seo_items[$key] = $menu_item; } else { $plugin_items[$key] = $menu_item; } } ?>
$menu_item) { if ($key !== 'dashboard' && $current_page === $key) { $has_active_seo = true; break; } } ?>
$menu_item) { if ($key === 'dashboard') { continue; } $is_active = ($current_page === $key); $icon = $menu_icons[$key] ?? 'admin-generic'; $page_url = '?page=' . $page_slug . $menu_item['slug_suffix']; ?>
$menu_item) { if ($key !== 'report_issue' && $current_page === $key) { $has_active_plugin = true; break; } } ?>
$menu_item) { if ($key === 'report_issue') { continue; } $is_active = ($current_page === $key); $icon = $menu_icons[$key] ?? 'admin-generic'; $page_url = '?page=' . $page_slug . $menu_item['slug_suffix']; ?>
Report Issue
resolve_logo_data(); $searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : ''; $otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : ''; $is_integrated = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general_settings); $current_theme = get_option('metasync_theme', 'dark'); ?>
Logo

is_heartbeat_connected($general_settings); $plugin_name = Metasync::get_effective_plugin_name(); if ($is_synced) { $otto_uuid = $general_settings['otto_pixel_uuid'] ?? ''; if (!empty($otto_uuid)) { $status_emoji = '🟢'; // Green circle for synced $title = $plugin_name . ' - Synced (Heartbeat API connectivity verified)'; $status_class = 'searchatlas-synced'; } else { $status_emoji = '🟡'; // Yellow circle for warning $title = $plugin_name . ' - Connected but OTTO UUID is missing — deploys will not work. Please reconnect.'; $status_class = 'searchatlas-warning'; } } else { $status_emoji = '🔴'; // Red circle for not synced $title = $plugin_name . ' - Not Synced (Heartbeat API not responding or unreachable)'; $status_class = 'searchatlas-not-synced'; } $admin_bar_title = $plugin_name . ' ' . $status_emoji; $node = array( 'title' => $admin_bar_title, 'href' => admin_url('admin.php?page=' . Metasync_Admin::$page_slug), 'meta_title' => $title, 'meta_class' => $status_class, ); set_transient(self::CACHE_KEY, $node, apply_filters('metasync_admin_bar_status_cache_ttl', 5 * MINUTE_IN_SECONDS)); } $wp_admin_bar->add_node(array( 'id' => 'searchatlas-status', 'title' => $node['title'], 'href' => $node['href'], 'meta' => array( 'title' => $node['meta_title'], 'class' => $node['meta_class'], ), )); } /** * Invalidate the admin bar status cache so the next page load recomputes status. */ public static function invalidate_admin_bar_status_cache() { delete_transient(self::CACHE_KEY); } // ------------------------------------------------------------------ // Yoast-style 3-column layout helpers // ------------------------------------------------------------------ /** * Open the 3-column page layout. * Call this at the start of every admin page callback instead of * render_plugin_header() + render_navigation_menu(). * Close with render_layout_close(). * * @param string $page_title Human-readable page title. * @param string $current_page Key matching get_available_menu_items() (e.g. 'general'). * @param string $description Optional subtitle shown below the title. */ public function render_layout_open($page_title = '', $current_page = '', $description = '') { $theme = esc_attr(get_option('metasync_theme', 'dark')); $plugin_name = Metasync::get_effective_plugin_name(); $general = Metasync::get_option('general') ?? []; $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general); // badge reflects confirmed heartbeat health (connected / stale / // disconnected), not raw API-key presence. $badge = Metasync_Heartbeat_Manager::instance()->get_connection_badge($general); $logo = $this->resolve_logo_data(); $current_theme = get_option('metasync_theme', 'dark'); ?>
'; foreach (array('dark', 'light') as $t) { if (!empty($wl_palette[$t]) && is_array($wl_palette[$t])) { $selector = ($t === 'dark') ? ':root, [data-theme="dark"]' : '[data-theme="light"]'; $vars = ''; foreach ($wl_palette[$t] as $var_name => $color) { // Skip gradient partial keys — handled below if (strpos($var_name, 'gradient-') !== false) continue; if (preg_match('/^dashboard-[a-z-]+$/', $var_name) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $color)) { $vars .= '--' . esc_attr($var_name) . ':' . esc_attr($color) . ';'; } } // Build gradient overrides from paired from/to colors $gradient_pairs = array( 'dashboard-gradient-primary' => array('dashboard-gradient-primary-from', 'dashboard-gradient-primary-to'), 'dashboard-gradient-accent' => array('dashboard-gradient-accent-from', 'dashboard-gradient-accent-to'), ); foreach ($gradient_pairs as $grad_var => $pair) { $from = isset($wl_palette[$t][$pair[0]]) ? $wl_palette[$t][$pair[0]] : ''; $to = isset($wl_palette[$t][$pair[1]]) ? $wl_palette[$t][$pair[1]] : ''; if ($from && $to && preg_match('/^#[0-9a-fA-F]{3,8}$/', $from) && preg_match('/^#[0-9a-fA-F]{3,8}$/', $to)) { $vars .= '--' . esc_attr($grad_var) . ':linear-gradient(135deg,' . esc_attr($from) . ' 0%,' . esc_attr($to) . ' 100%);'; } } if ($vars) { echo $selector . '{' . $vars . '}'; } } } echo ''; } ?>
<?php echo esc_attr($plugin_name); ?>

, renders promo sidebar, closes
.metasync-layout and
.wrap } /** * Close the 3-column layout opened by render_layout_open(). * * @param bool $show_promo Whether to render the right promo sidebar. Default true. * Pass false for full-width pages (e.g. dashboard iframe). */ public function render_layout_close($show_promo = true) { ?> is_heartbeat_connected($general); ?> get_available_menu_items(); // Dashicons names (without 'dashicons-' prefix) — monochrome, color set by CSS $icons = [ 'dashboard' => 'dashboard', 'seo_controls' => 'search', 'monitor_404' => 'warning', 'redirections' => 'undo', 'xml_sitemap' => 'networking', 'robots_txt' => 'shield', 'site_verification'=> 'yes-alt', 'instant_index' => 'performance', 'google_console' => 'chart-area', 'bing_console' => 'chart-bar', 'import_seo' => 'download', 'general' => 'admin-settings', 'schema_markup' => 'tag', 'local_business' => 'building', 'breadcrumbs' => 'menu', 'code_snippets' => 'editor-code', 'code_minification'=> 'media-code', 'custom_pages' => 'admin-page', 'compatibility' => 'admin-tools', 'sync_log' => 'list-view', 'bot_statistics' => 'visibility', 'report_issue' => 'sos', 'media_optimization'=> 'images-alt2', 'seo_health' => 'heart' ]; $seo_items = []; $plugin_items = []; foreach ($menu_items as $key => $item) { if (($item['group'] ?? 'plugin') === 'seo') { $seo_items[$key] = $item; } else { $plugin_items[$key] = $item; } } ?>

Connect

Link your site to to unlock , keyword data, and automated SEO.

Connect Now

Active

Your site is connected and is optimizing pages automatically.

View Dashboard View Dashboard

Quick Links