PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 2.7.0 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 All 141 releases
← All changes | includes/class-metasync-admin-navigation.php +68 -61 2.6.22 → 2.7.1 View file →
@@ -286,12 +286,8 @@
286 286 if (empty($whitelabel_settings['hide_indexation_control'])) {
287 287 $menu_items['seo_controls'] = ['title' => 'Indexation Control', 'slug_suffix' => '-seo-controls'];
288 288 }
289 289
290 - if ($general_options['enable_googleinstantindex'] ?? false) {
291 - $menu_items['instant_index'] = ['title' => 'Instant Indexing', 'slug_suffix' => '-instant-index'];
292 - }
293 -
294 290 if ($general_options['enable_google_console'] ?? false) {
295 291 $menu_items['google_console'] = ['title' => 'Google Console', 'slug_suffix' => '-google-console'];
296 292 }
297 293
@@ -506,20 +502,8 @@
506 502 'group' => 'seo'
507 503 ];
508 504 }
509 505
510 - // Instant Indexing - setting now stored in seo_controls (moved from Settings to Indexation Control)
511 - $seo_controls = Metasync::get_option('seo_controls');
512 - if ($seo_controls['enable_googleinstantindex'] ?? false) {
513 - $menu_items['instant_index'] = [
514 - 'title' => 'Instant Indexing',
515 - 'slug_suffix' => '-instant-index',
516 - 'callback' => 'create_admin_google_instant_index_page',
517 - 'internal_nav' => 'Instant Indexing',
518 - 'group' => 'seo'
519 - ];
520 - }
521 -
522 506 if ($general_options['enable_google_console'] ?? false) {
523 507 $menu_items['google_console'] = [
524 508 'title' => 'Google Console',
525 509 'slug_suffix' => '-google-console',
@@ -787,14 +771,19 @@
787 771
788 772 // ── Connect slug ──────────────────────────────────────────────────
789 773 $connect_slug = $menu_slug . '-connect';
790 774
775 + // Keep a slug-to-title map so hidden pages can still provide WordPress
776 + // with a browser-tab title when they are registered without a parent.
777 + $admin_page_titles = array();
778 +
791 779 // Local helper: register a submenu page under the plugin parent slug
792 780 // when the item keeps its WordPress admin sidebar row, or under an
793 781 // empty parent slug (hidden from the sidebar but still reachable)
794 782 // otherwise. The Settings page (general) is registered against the
795 783 // bare $menu_slug so it doubles as the parent menu callback.
796 - $register_submenu = function($item_key, $page_title, $menu_title, $capability, $menu_page_slug, $callback) use ($menu_slug) {
784 + $register_submenu = function($item_key, $page_title, $menu_title, $capability, $menu_page_slug, $callback) use ($menu_slug, &$admin_page_titles) {
785 + $admin_page_titles[$menu_page_slug] = $page_title;
797 786 $parent = self::item_keeps_wp_sidebar_row($item_key) ? $menu_slug : '';
798 787 add_submenu_page($parent, $page_title, $menu_title, $capability, $menu_page_slug, $callback);
799 788 };
800 789
@@ -828,13 +817,8 @@
828 817
829 818 // Site Verification (in-page sidebar only in short mode)
830 819 $register_submenu('site_verification', 'Site Verification', 'Site Verification', $menu_capability, $menu_slug . '-search-engine-verify', array($admin, 'create_admin_search_engine_verification_page'));
831 820
832 - // Instant Indexing (conditional)
833 - if ($seo_controls['enable_googleinstantindex'] ?? false) {
834 - $register_submenu('instant_index', 'Instant Indexing', 'Instant Indexing', $menu_capability, $menu_slug . '-instant-index', array($admin, 'create_admin_google_instant_index_page'));
835 - }
836 -
837 821 // Google Console (conditional)
838 822 if ($general_options['enable_google_console'] ?? false) {
839 823 $register_submenu('google_console', 'Google Console', 'Google Console', $menu_capability, $menu_slug . '-google-console', array($admin, 'create_admin_google_console_page'));
840 824 }
@@ -908,11 +892,36 @@
908 892 add_submenu_page($menu_slug, $connect_label, $connect_label, $menu_capability, $connect_slug, array($admin, 'create_admin_settings_page'));
909 893 }
910 894
911 895 // ── Hidden pages (no sidebar entry needed) ────────────────────────
896 + $admin_page_titles[$menu_slug . '-setup-wizard'] = 'Setup Wizard';
912 897 add_submenu_page('', 'Setup Wizard', 'Setup Wizard', $menu_capability, $menu_slug . '-setup-wizard', array($admin->setup_wizard, 'render_wizard_page'));
913 898
899 + // WordPress cannot resolve titles for pages registered under an empty
900 + // parent, even though those pages remain reachable by URL. Restore the
901 + // standard admin title format for our registered pages only.
902 + add_filter('admin_title', function($admin_title, $title) use (&$admin_page_titles) {
903 + $current_page = isset($_GET['page']) && is_string($_GET['page']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page slug for the admin title
904 + ? sanitize_text_field(wp_unslash($_GET['page'])) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page slug for the admin title
905 + : '';
914 906
907 + if (empty($title) && isset($admin_page_titles[$current_page])) {
908 + $site_title = is_network_admin() || is_user_admin()
909 + ? get_network()->site_name
910 + : get_bloginfo('name');
911 +
912 + $admin_title = sprintf(
913 + /* translators: 1: Admin page title, 2: Site or network title. */
914 + __('%1$s ‹ %2$s — WordPress', 'metasync'),
915 + $admin_page_titles[$current_page],
916 + $site_title
917 + );
918 + }
919 +
920 + return $admin_title;
921 + }, 10, 2);
922 +
923 +
915 924 // Rename auto-generated first submenu from plugin name to "Settings" and reorder
916 925 add_action('admin_menu', function() use ($menu_slug) {
917 926 global $submenu;
918 927 if (!isset($submenu[$menu_slug])) {
@@ -1448,10 +1457,10 @@
1448 1457
1449 1458 <?php
1450 1459 $page_slug = Metasync_Admin::$page_slug;
1451 1460 $is_plugin_page = (
1452 - (isset($_GET['page']) && strpos($_GET['page'], $page_slug) !== false) ||
1453 - (isset($_GET['page']) && strpos($_GET['page'], 'searchatlas') !== false)
1461 + (isset($_GET['page']) && strpos($_GET['page'], $page_slug) !== false) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for an inline config script
1462 + (isset($_GET['page']) && strpos($_GET['page'], 'searchatlas') !== false) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for an inline config script
1454 1463 );
1455 1464 if ($is_plugin_page):
1456 1465 ?>
1457 1466 <script type="text/javascript">
@@ -1747,15 +1756,19 @@
1747 1756 {
1748 1757 ?>
1749 1758 </main><!-- /.metasync-layout-main -->
1750 1759
1751 - <?php if ($show_promo):
1752 - $general = Metasync::get_option('general') ?? [];
1753 - $is_connected = Metasync_Heartbeat_Manager::instance()->is_heartbeat_connected($general);
1760 + <?php
1761 + // The sidebar exists to get an unconnected site connected. Once the
1762 + // site is linked it has nothing further to offer, so the whole
1763 + // column is dropped and the grid reflows to two columns.
1764 + $show_promo = $show_promo && ! Metasync_Heartbeat_Manager::instance()
1765 + ->is_heartbeat_connected(Metasync::get_option('general') ?? []);
1766 + if ($show_promo):
1754 1767 ?>
1755 1768 <!-- Right promo sidebar -->
1756 1769 <aside class="metasync-layout-promo">
1757 - <?php $this->render_promo_sidebar($is_connected); ?>
1770 + <?php $this->render_promo_sidebar(); ?>
1758 1771 </aside>
1759 1772 <?php endif; ?>
1760 1773
1761 1774 </div><!-- /.metasync-layout -->
@@ -1810,11 +1823,19 @@
1810 1823 $plugin_items[$key] = $item;
1811 1824 }
1812 1825 }
1813 1826
1827 + $total_items = count($seo_items) + count($plugin_items);
1828 +
1814 1829 ?>
1815 1830 <nav class="metasync-sidenav">
1816 1831
1832 + <?php if ($total_items === 0): ?>
1833 + <div class="metasync-sidenav-empty">
1834 + <?php esc_html_e('No pages are available.', 'metasync'); ?>
1835 + </div>
1836 + <?php else: ?>
1837 +
1817 1838 <!-- SEO Features group -->
1818 1839 <div class="metasync-sidenav-group">
1819 1840 <div class="metasync-sidenav-group-title">SEO Features</div>
1820 1841 <ul>
@@ -1880,8 +1901,10 @@
1880 1901 </ul>
1881 1902 </div>
1882 1903 <?php endif; ?>
1883 1904
1905 + <?php endif; ?>
1906 +
1884 1907 </nav>
1885 1908 <?php
1886 1909 }
1887 1910
@@ -1887,17 +1910,22 @@
1887 1910
1888 1911 /**
1889 1912 * Render the right promotional sidebar.
1890 1913 *
1891 - * @param bool $is_connected Whether the plugin is authenticated.
1914 + * Only reached while the site is unconnected; the caller drops the
1915 + * whole column once the plugin is linked.
1892 1916 */
1893 - public function render_promo_sidebar($is_connected = false)
1917 + public function render_promo_sidebar()
1894 1918 {
1895 1919 $plugin_name = Metasync::get_effective_plugin_name();
1896 1920 $otto_name = Metasync::get_whitelabel_otto_name();
1897 1921 $settings_url = esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug));
1898 1922 $homepage = Metasync::HOMEPAGE_DOMAIN;
1899 - $is_default_brand = ( $plugin_name === 'Search Atlas' );
1923 + $docs_url = Metasync::DOCUMENTATION_DOMAIN;
1924 + // These links point at Search Atlas properties, so they only belong
1925 + // here while the plugin runs under its own name AND branding — a
1926 + // reseller can leave the name untouched and still be whitelabeled.
1927 + $is_default_brand = ( $plugin_name === 'Search Atlas' ) && ! Metasync::is_whitelabel_enabled();
1900 1928 $whitelabel = Metasync::get_whitelabel_settings();
1901 1929 $custom_links = isset($whitelabel['quick_links']) && is_array($whitelabel['quick_links'])
1902 1930 ? array_filter($whitelabel['quick_links'], function($l) { return !empty($l['url']); })
1903 1931 : [];
@@ -1902,9 +1930,8 @@
1902 1930 ? array_filter($whitelabel['quick_links'], function($l) { return !empty($l['url']); })
1903 1931 : [];
1904 1932 ?>
1905 1933
1906 - <?php if (!$is_connected): ?>
1907 1934 <!-- Connect CTA card -->
1908 1935 <div class="metasync-promo-card metasync-promo-card--connect">
1909 1936 <div class="metasync-promo-card-header">
1910 1937 <div class="metasync-promo-card-icon">
@@ -1924,36 +1951,8 @@
1924 1951 <a href="<?php echo $settings_url; ?>" class="metasync-promo-btn metasync-promo-btn--primary">
1925 1952 Connect Now
1926 1953 </a>
1927 1954 </div>
1928 - <?php else: ?>
1929 - <!-- Connected — feature highlights -->
1930 - <div class="metasync-promo-card metasync-promo-card--accent">
1931 - <div class="metasync-promo-card-header">
1932 - <div class="metasync-promo-card-icon">
1933 - <span class="dashicons dashicons-performance"></span>
1934 - </div>
1935 - <div>
1936 - <h3><?php echo esc_html($otto_name); ?> Active</h3>
1937 - </div>
1938 - </div>
1939 - <p class="metasync-promo-tagline">Your site is connected and <?php echo esc_html($otto_name); ?> is optimizing pages automatically.</p>
1940 - <ul class="metasync-promo-benefits">
1941 - <li><span class="promo-check">&#10003;</span> Schema markup auto-applied</li>
1942 - <li><span class="promo-check">&#10003;</span> Meta titles &amp; descriptions optimized</li>
1943 - <li><span class="promo-check">&#10003;</span> Internal linking suggestions active</li>
1944 - </ul>
1945 - <?php if ($is_default_brand): ?>
1946 - <a href="<?php echo esc_url($homepage); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1947 - View <?php echo esc_html($plugin_name); ?> Dashboard
1948 - </a>
1949 - <?php elseif (!empty($whitelabel['domain'])): ?>
1950 - <a href="<?php echo esc_url($whitelabel['domain']); ?>" target="_blank" rel="noopener" class="metasync-promo-btn metasync-promo-btn--outline">
1951 - View <?php echo esc_html($plugin_name); ?> Dashboard
1952 - </a>
1953 - <?php endif; ?>
1954 - </div>
1955 - <?php endif; ?>
1956 1955
1957 1956 <?php
1958 1957 // Quick Links: show default links for default brand, custom links if whitelabeled + provided, hide if whitelabeled + none
1959 1958 $show_quick_links = $is_default_brand || !empty($custom_links);
@@ -1963,12 +1962,20 @@
1963 1962 <div class="metasync-promo-card">
1964 1963 <h3 style="margin:0 0 12px;font-size:13px;font-weight:700;color:var(--dashboard-text-primary);">Quick Links</h3>
1965 1964 <ul class="metasync-promo-links">
1966 1965 <?php if ($is_default_brand): ?>
1966 + <li><a href="<?php echo esc_url($docs_url); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-book-alt"></span> Documentation</a></li>
1967 1967 <li><a href="<?php echo esc_url($homepage . '/blog/'); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-rss"></span> SEO Blog</a></li>
1968 - <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>
1968 + <li><a href="<?php echo esc_url($homepage . '/contact/'); ?>" target="_blank" rel="noopener"><span class="dashicons dashicons-email-alt"></span> Contact Support</a></li>
1969 1969 <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>
1970 + <?php
1971 + // Mirrors the gate that decides whether the page is registered
1972 + // at all. Without it the link outlives the page for any user
1973 + // the access rules hide it from, and leads nowhere.
1974 + if (Metasync_Access_Control::user_can_access('hide_report_issue')):
1975 + ?>
1970 1976 <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>
1977 + <?php endif; ?>
1971 1978 <?php else: ?>
1972 1979 <?php foreach ($custom_links as $link): ?>
1973 1980 <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>
1974 1981 <?php endforeach; ?>