PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
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-pages.php +58 -115 2.6.25 → 2.7.2 View file →
@@ -217,14 +217,14 @@
217 217 // ------------------------------------------------------------------
218 218
219 219 public function create_admin_settings_page()
220 220 {
221 - if (isset($_REQUEST['metasync_password_reset'])) {
221 + if (isset($_REQUEST['metasync_password_reset'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing check, the reset form verifies its own nonce
222 222 Metasync_Admin_Ajax::instance()->render_password_reset_page();
223 223 return;
224 224 }
225 225
226 - $active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
226 + $active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab selection
227 227
228 228 $whitelabel_settings = Metasync::get_whitelabel_settings();
229 229
230 230 $user_password = $whitelabel_settings['settings_password'] ?? '';
@@ -250,9 +250,9 @@
250 250
251 251 $auth = new Metasync_Auth_Manager('whitelabel', 1800);
252 252 $password_validated = $auth->has_access();
253 253
254 - if (isset($_POST['whitelabel_password_submit']) && !$password_validated) {
254 + if (isset($_POST['whitelabel_password_submit']) && !$password_validated) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- read-only access check for a display message
255 255 $password_error = 'Incorrect password. Please try again.';
256 256 }
257 257 }
258 258
@@ -269,8 +269,18 @@
269 269 $this->admin->render_layout_open('Settings', $settings_current_page, 'Manage all plugin settings and configuration.');
270 270 ?>
271 271 <?php
272 272 /*
273 + * options.php redirects back here after the native (no-JavaScript)
274 + * settings submit. Scoped to metasync_messages so field-level
275 + * rejections (e.g. invalid white-label branding) print without the
276 + * generic "Settings saved." notice that would otherwise accompany
277 + * a rejected save.
278 + */
279 + settings_errors('metasync_messages');
280 + ?>
281 + <?php
282 + /*
273 283 # Temporarily commented out: Clear Cache notice and button (can be re-enabled later)
274 284
275 285 * <div class="notice notice-success">
276 286 * <p>
@@ -394,8 +404,11 @@
394 404
395 405 <?php
396 406 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
397 407 ?>
408 + <p style="margin: 20px 0 0 0; font-size: 13px; color: var(--dashboard-text-secondary);">
409 + Breadcrumb settings now live on their own page: <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-breadcrumbs')); ?>">Breadcrumbs</a>.
410 + </p>
398 411 </div>
399 412
400 413 <div class="dashboard-card">
401 414 <h2>Content Genius Synchronization<?php Metasync::render_tooltip_icon('cg_sync_section', sprintf('Syncing sends this site\'s categories and authors to %s, so Content Genius can assign the right category and author when publishing articles to this site.', Metasync::get_effective_plugin_name())); ?></h2>
@@ -780,21 +793,21 @@
780 793 }
781 794 });
782 795
783 796 function hasPassword() {
784 - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
797 + var passwordField = $('input[name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][settings_password]"]');
785 798 var passwordValue = passwordField.val();
786 799 return passwordValue && passwordValue.length > 0;
787 800 }
788 801
789 802 function hasRecoveryEmail() {
790 - var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
803 + var recoveryEmailField = $('input[name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][recovery_email]"]');
791 804 var emailValue = recoveryEmailField.val();
792 805 return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue);
793 806 }
794 807
795 - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
796 - var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
808 + var passwordField = $('input[name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][settings_password]"]');
809 + var recoveryEmailField = $('input[name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][recovery_email]"]');
797 810
798 811 passwordField.on('blur', function() {
799 812 if (hasPassword() && !hasRecoveryEmail()) {
800 813 showModal(
@@ -832,9 +845,9 @@
832 845 'You must set a <strong>Settings Password</strong> before enabling "Hide Settings".<br><br>Please scroll up to the <strong>Branding</strong> section and set a password first.'
833 846 );
834 847
835 848 setTimeout(function() {
836 - $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]').focus();
849 + $('input[name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][settings_password]"]').focus();
837 850 }, 300);
838 851
839 852 return false;
840 853 }
@@ -905,10 +918,10 @@
905 918 </div>
906 919 <?php endif; ?>
907 920
908 921 <?php
909 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
910 - $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
922 + if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice
923 + $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice
911 924 ?>
912 925 <div class="notice notice-success inline" style="margin-bottom: 20px;">
913 926 <p>
914 927 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -915,9 +928,9 @@
915 928 </p>
916 929 </div>
917 930 <?php endif; ?>
918 931
919 - <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?>
932 + <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
920 933 <div class="notice notice-info inline" style="margin-bottom: 20px;">
921 934 <p>
922 935 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
923 936 </p>
@@ -923,9 +936,9 @@
923 936 </p>
924 937 </div>
925 938 <?php endif; ?>
926 939
927 - <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?>
940 + <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
928 941 <div class="notice notice-success inline" style="margin-bottom: 20px;">
929 942 <p>
930 943 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
931 944 </p>
@@ -931,9 +944,9 @@
931 944 </p>
932 945 </div>
933 946 <?php endif; ?>
934 947
935 - <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?>
948 + <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
936 949 <div class="notice notice-error inline" style="margin-bottom: 20px;">
937 950 <p>
938 951 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
939 952 </p>
@@ -970,9 +983,9 @@
970 983 'method': 'post',
971 984 'action': ''
972 985 });
973 986
974 - logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); ?>');
987 + logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- nonce markup from wp_nonce_field() is safe by construction; kses-based escaping strips the <input> and breaks the logout nonce ?>');
975 988
976 989 logoutForm.append($('<input>', {
977 990 'type': 'hidden',
978 991 'name': 'whitelabel_logout',
@@ -1044,9 +1057,9 @@
1044 1057 $.ajax({
1045 1058 url: ajaxUrl,
1046 1059 type: 'POST',
1047 1060 dataType: 'json',
1048 - data: { action: action, nonce: '<?php echo wp_create_nonce('metasync_nonce'); ?>' },
1061 + data: { action: action, nonce: '<?php echo esc_attr(wp_create_nonce('metasync_nonce')); ?>' },
1049 1062 timeout: 35000,
1050 1063 success: function(response, textStatus, xhr) {
1051 1064 try {
1052 1065 if (response && response.success && response.data) {
@@ -1114,22 +1127,22 @@
1114 1127 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1115 1128
1116 1129 html += '<div class="test-result-item ' + statusClass + '">';
1117 1130 html += '<div class="test-result-header">';
1118 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1131 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
1119 1132 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1120 1133 html += '</div>';
1121 1134
1122 1135 html += '<div class="test-result-details">';
1123 - html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
1124 - html += '<p><strong>Status:</strong> ' + result.status + '</p>';
1136 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
1137 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
1125 1138
1126 1139 if (result.status_code) {
1127 - html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
1140 + html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + escapeHtml(result.status_code) + '</span></p>';
1128 1141 }
1129 1142
1130 1143 if (result.error) {
1131 - html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
1144 + html += '<p><strong>Error:</strong> <span class="error-message">' + escapeHtml(result.error) + '</span></p>';
1132 1145 }
1133 1146
1134 1147 if (result.body) {
1135 1148 html += '<p><strong>Response Body:</strong></p>';
@@ -1139,9 +1152,9 @@
1139 1152 if (result.headers && Object.keys(result.headers).length > 0) {
1140 1153 html += '<p><strong>Response Headers:</strong></p>';
1141 1154 html += '<pre class="response-headers">';
1142 1155 Object.keys(result.headers).forEach(function(key) {
1143 - html += key + ': ' + result.headers[key] + '\n';
1156 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
1144 1157 });
1145 1158 html += '</pre>';
1146 1159 }
1147 1160
@@ -1154,9 +1167,9 @@
1154 1167 html += '<p><strong>Parsed Response:</strong></p>';
1155 1168 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1156 1169 }
1157 1170
1158 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1171 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
1159 1172 html += '</div>';
1160 1173 html += '</div>';
1161 1174 });
1162 1175
@@ -1171,9 +1184,9 @@
1171 1184 '>': '&gt;',
1172 1185 '"': '&quot;',
1173 1186 '\'': '&#039;'
1174 1187 };
1175 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1188 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
1176 1189 }
1177 1190 });
1178 1191 </script>
1179 1192
@@ -1306,41 +1319,41 @@
1306 1319 }
1307 1320 </style>
1308 1321
1309 1322 <?php if ($active_tab === 'advanced'): ?>
1310 - <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?>
1323 + <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1311 1324 <div class="notice notice-success is-dismissible">
1312 1325 <p><strong>✅ Success!</strong> All plugin settings have been cleared successfully and a new Plugin Auth Token has been generated. Please reconfigure the plugin as needed.</p>
1313 1326 </div>
1314 1327 <?php endif; ?>
1315 1328
1316 - <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?>
1329 + <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1317 1330 <div class="notice notice-error is-dismissible">
1318 1331 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1319 1332 </div>
1320 1333 <?php endif; ?>
1321 1334
1322 - <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?>
1335 + <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1323 1336 <div class="notice notice-success is-dismissible">
1324 1337 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1325 1338 </div>
1326 1339 <?php endif; ?>
1327 1340
1328 - <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?>
1341 + <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1329 1342 <div class="notice notice-error is-dismissible">
1330 1343 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1331 1344 </div>
1332 1345 <?php endif; ?>
1333 1346
1334 - <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?>
1347 + <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1335 1348 <div class="notice notice-success is-dismissible">
1336 1349 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1337 1350 </div>
1338 1351 <?php endif; ?>
1339 1352
1340 - <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?>
1353 + <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1341 1354 <div class="notice notice-error is-dismissible">
1342 - <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p>
1355 + <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only error text, escaped on output ?></p>
1343 1356 </div>
1344 1357 <?php endif; ?>
1345 1358
1346 1359 <?php endif; ?>
@@ -1362,9 +1375,9 @@
1362 1375 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Access your <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> dashboard to view analytics, manage SEO settings, and monitor your site performance.</p>
1363 1376 <?php
1364 1377 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1365 1378 echo '<p style="color: #d54e21; margin-bottom: 15px;">Authentication required: Please authenticate with your ' . esc_html(Metasync::get_effective_plugin_name()) . ' account and save your auth token in general settings.</p>';
1366 - echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-secondary">Go to Settings</a>';
1379 + echo '<a href="' . esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug)) . '" class="button button-secondary">Go to Settings</a>';
1367 1380 } else {
1368 1381 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>';
1369 1382 }
1370 1383 ?>
@@ -1374,8 +1387,15 @@
1374 1387 }
1375 1388
1376 1389 public function create_admin_robots_txt_page()
1377 1390 {
1391 + // The form below rewrites the site's physical robots.txt — restrict
1392 + // that to site admins, not just anyone who can open the page via
1393 + // plugin_access_roles. The nonce is CSRF protection, not authorization.
1394 + if (!empty($_POST) && !current_user_can('manage_options')) {
1395 + wp_die(esc_html__('Sorry, you are not allowed to manage robots.txt settings.', 'metasync'));
1396 + }
1397 +
1378 1398 ?>
1379 1399 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1380 1400
1381 1401 <?php
@@ -1396,8 +1416,11 @@
1396 1416
1397 1417 if (is_wp_error($result)) {
1398 1418 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1399 1419 } else {
1420 + Metasync_PostHog::feature('robots_txt_saved', array(
1421 + 'warning_count' => count($validation['warnings']),
1422 + ));
1400 1423 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1401 1424
1402 1425 if (!empty($validation['warnings'])) {
1403 1426 foreach ($validation['warnings'] as $warning) {
@@ -1438,84 +1461,8 @@
1438 1461 {
1439 1462 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1440 1463 }
1441 1464
1442 - public function create_admin_xml_sitemap_page()
1443 - {
1444 - require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1445 -
1446 - $sitemap_generator = new Metasync_Sitemap_Generator();
1447 -
1448 - if (isset($_POST['metasync_sitemap_nonce'])) {
1449 - check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1450 -
1451 - if (isset($_POST['generate_sitemap'])) {
1452 - $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1453 -
1454 - $result = $sitemap_generator->generate_sitemap();
1455 -
1456 - if (is_wp_error($result)) {
1457 - error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1458 - echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1459 - } elseif (false === $result) {
1460 - echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed: the sitemap data could not be stored, so no sitemap is being served. Check your object cache and database write settings, then try again.', 'metasync') . '</p></div>';
1461 - } else {
1462 - $message = esc_html__('Sitemap generated successfully!', 'metasync');
1463 - if ($disabled_plugins) {
1464 - $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1465 - }
1466 -
1467 - $robots_result = get_transient('metasync_sitemap_robots_updated');
1468 - if ($robots_result && $robots_result['success']) {
1469 - if ($robots_result['action'] === 'added') {
1470 - $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1471 - } elseif ($robots_result['action'] === 'updated') {
1472 - $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1473 - } elseif ($robots_result['action'] === 'created') {
1474 - $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1475 - }
1476 - delete_transient('metasync_sitemap_robots_updated');
1477 - }
1478 -
1479 - echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1480 - }
1481 - } elseif (isset($_POST['enable_auto_update'])) {
1482 - update_option('metasync_sitemap_auto_update', true);
1483 - $sitemap_generator->setup_auto_update_hooks();
1484 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1485 - } elseif (isset($_POST['disable_auto_update'])) {
1486 - update_option('metasync_sitemap_auto_update', false);
1487 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1488 - } elseif (isset($_POST['delete_sitemap'])) {
1489 - $deleted = $sitemap_generator->delete_sitemap();
1490 - if ($deleted) {
1491 - update_option('metasync_sitemap_auto_update', false);
1492 - // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1493 - delete_option('metasync_disable_wp_sitemap');
1494 - echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1495 - } else {
1496 - echo '<div class="notice notice-error"><p>' . esc_html__('Failed to delete sitemap. The file may not exist or is not writable.', 'metasync') . '</p></div>';
1497 - }
1498 - } elseif (isset($_POST['enable_other_sitemaps'])) {
1499 - $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1500 - if ($enabled_plugins) {
1501 - echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1502 - } else {
1503 - echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1504 - }
1505 - }
1506 - }
1507 -
1508 - $sitemap_exists = $sitemap_generator->sitemap_exists();
1509 - $sitemap_url = $sitemap_generator->get_sitemap_url();
1510 - $url_count = $sitemap_generator->count_urls();
1511 - $last_generated = $sitemap_generator->get_last_generated_time();
1512 - $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1513 - $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1514 -
1515 - require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1516 - }
1517 -
1518 1465 public function create_admin_custom_pages_page()
1519 1466 {
1520 1467 ?>
1521 1468 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
@@ -1566,12 +1513,14 @@
1566 1513 }
1567 1514
1568 1515 public function create_admin_breadcrumbs_page()
1569 1516 {
1570 - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1571 1517 ?>
1572 1518 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1573 1519
1520 + <?php /* options.php redirects back here with ?settings-updated=true; without this call the save confirmation never prints */ ?>
1521 + <?php settings_errors(); ?>
1522 +
1574 1523 <form method="post" action="options.php">
1575 1524 <div class="dashboard-card">
1576 1525 <h2>Breadcrumbs Settings</h2>
1577 1526 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
@@ -1576,9 +1525,9 @@
1576 1525 <h2>Breadcrumbs Settings</h2>
1577 1526 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1578 1527 <?php
1579 1528 settings_fields(Metasync_Admin::option_group);
1580 - do_settings_sections($page_slug);
1529 + Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1581 1530 submit_button();
1582 1531 ?>
1583 1532 </div>
1584 1533 </form>
@@ -1698,14 +1647,8 @@
1698 1647 </div>
1699 1648 </form>
1700 1649 <?php $this->admin->render_layout_close(); ?>
1701 1650 <?php
1702 - }
1703 -
1704 - public function create_admin_google_instant_index_page()
1705 - {
1706 - // Delegates to the admin class which uses the shared credentials view
1707 - $this->admin->create_admin_google_instant_index_page();
1708 1651 }
1709 1652
1710 1653 public function create_admin_google_console_page()
1711 1654 {