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 +65 -165 2.6.23 → 2.7.2 View file →
@@ -217,10 +217,15 @@
217 217 // ------------------------------------------------------------------
218 218
219 219 public function create_admin_settings_page()
220 220 {
221 - $active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
221 + if (isset($_REQUEST['metasync_password_reset'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing check, the reset form verifies its own nonce
222 + Metasync_Admin_Ajax::instance()->render_password_reset_page();
223 + return;
224 + }
222 225
226 + $active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab selection
227 +
223 228 $whitelabel_settings = Metasync::get_whitelabel_settings();
224 229
225 230 $user_password = $whitelabel_settings['settings_password'] ?? '';
226 231 $hide_settings_enabled = !empty($whitelabel_settings['hide_settings']);
@@ -245,9 +250,9 @@
245 250
246 251 $auth = new Metasync_Auth_Manager('whitelabel', 1800);
247 252 $password_validated = $auth->has_access();
248 253
249 - 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
250 255 $password_error = 'Incorrect password. Please try again.';
251 256 }
252 257 }
253 258
@@ -264,8 +269,18 @@
264 269 $this->admin->render_layout_open('Settings', $settings_current_page, 'Manage all plugin settings and configuration.');
265 270 ?>
266 271 <?php
267 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 + /*
268 283 # Temporarily commented out: Clear Cache notice and button (can be re-enabled later)
269 284
270 285 * <div class="notice notice-success">
271 286 * <p>
@@ -333,8 +348,9 @@
333 348 </button>
334 349 </div>
335 350 </form>
336 351
352 + <?php if (current_user_can('manage_options')): ?>
337 353 <div style="text-align: center; margin-top: 20px;">
338 354 <a href="#" id="metasync-forgot-password-link" style="color: #2271b1; text-decoration: none; font-size: 14px;">
339 355 Forgot Password?
340 356 </a>
@@ -339,64 +355,12 @@
339 355 Forgot Password?
340 356 </a>
341 357 <div id="metasync-recovery-message" style="margin-top: 15px; padding: 12px; border-radius: 6px; display: none;"></div>
342 358 </div>
359 + <?php endif; ?>
343 360
344 361 </div>
345 362
346 - <script>
347 - jQuery(document).ready(function($) {
348 - $('#whitelabel_password').focus();
349 -
350 - $('#whitelabel_password').on('keypress', function(e) {
351 - if (e.which === 13) {
352 - $(this).closest('form').submit();
353 - }
354 - });
355 -
356 - $('#metasync-forgot-password-link').on('click', function(e) {
357 - e.preventDefault();
358 -
359 - var $link = $(this);
360 - var $message = $('#metasync-recovery-message');
361 -
362 - $link.css('pointer-events', 'none').css('opacity', '0.6');
363 - $message.removeClass('success error').hide();
364 - $message.html('⏳ Sending recovery email...').css('background', '#f0f6fc').css('color', '#0c5ba5').css('border', '1px solid #cfe2f3').fadeIn(200);
365 -
366 - $.ajax({
367 - url: ajaxurl,
368 - type: 'POST',
369 - data: {
370 - action: 'metasync_recover_password',
371 - nonce: '<?php echo wp_create_nonce('metasync_recover_password_nonce'); ?>'
372 - },
373 - success: function(response) {
374 - $link.css('pointer-events', 'auto').css('opacity', '1');
375 -
376 - if (response.success) {
377 - $message.addClass('success').html('✅ ' + response.data.message)
378 - .css('background', '#d4edda')
379 - .css('color', '#155724')
380 - .css('border', '1px solid #c3e6cb');
381 - } else {
382 - $message.addClass('error').html('❌ ' + response.data.message)
383 - .css('background', '#f8d7da')
384 - .css('color', '#721c24')
385 - .css('border', '1px solid #f5c6cb');
386 - }
387 - },
388 - error: function() {
389 - $link.css('pointer-events', 'auto').css('opacity', '1');
390 - $message.addClass('error').html('❌ An error occurred. Please try again.')
391 - .css('background', '#f8d7da')
392 - .css('color', '#721c24')
393 - .css('border', '1px solid #f5c6cb');
394 - }
395 - });
396 - });
397 - });
398 - </script>
399 363 <?php
400 364 return;
401 365 } elseif (!$password_protection_enabled) {
402 366 ?>
@@ -410,8 +374,9 @@
410 374 }
411 375 ?>
412 376
413 377 <form method="post" action="options.php?tab=<?php echo esc_attr( $active_tab ); ?>" id="metaSyncGeneralSetting">
378 + <input type="hidden" name="metasync_pw_reset_generation" value="<?php echo esc_attr((string) (int) get_option('metasync_pw_reset_generation', 0)); ?>" />
414 379 <?php
415 380 settings_fields(Metasync_Admin::option_group);
416 381
417 382 wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce');
@@ -439,8 +404,11 @@
439 404
440 405 <?php
441 406 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
442 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>
443 411 </div>
444 412
445 413 <div class="dashboard-card">
446 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>
@@ -825,21 +793,21 @@
825 793 }
826 794 });
827 795
828 796 function hasPassword() {
829 - 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]"]');
830 798 var passwordValue = passwordField.val();
831 799 return passwordValue && passwordValue.length > 0;
832 800 }
833 801
834 802 function hasRecoveryEmail() {
835 - 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]"]');
836 804 var emailValue = recoveryEmailField.val();
837 805 return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue);
838 806 }
839 807
840 - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
841 - 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]"]');
842 810
843 811 passwordField.on('blur', function() {
844 812 if (hasPassword() && !hasRecoveryEmail()) {
845 813 showModal(
@@ -877,9 +845,9 @@
877 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.'
878 846 );
879 847
880 848 setTimeout(function() {
881 - $('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();
882 850 }, 300);
883 851
884 852 return false;
885 853 }
@@ -950,10 +918,10 @@
950 918 </div>
951 919 <?php endif; ?>
952 920
953 921 <?php
954 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
955 - $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
956 924 ?>
957 925 <div class="notice notice-success inline" style="margin-bottom: 20px;">
958 926 <p>
959 927 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -960,9 +928,9 @@
960 928 </p>
961 929 </div>
962 930 <?php endif; ?>
963 931
964 - <?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 ?>
965 933 <div class="notice notice-info inline" style="margin-bottom: 20px;">
966 934 <p>
967 935 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
968 936 </p>
@@ -968,9 +936,9 @@
968 936 </p>
969 937 </div>
970 938 <?php endif; ?>
971 939
972 - <?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 ?>
973 941 <div class="notice notice-success inline" style="margin-bottom: 20px;">
974 942 <p>
975 943 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
976 944 </p>
@@ -976,9 +944,9 @@
976 944 </p>
977 945 </div>
978 946 <?php endif; ?>
979 947
980 - <?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 ?>
981 949 <div class="notice notice-error inline" style="margin-bottom: 20px;">
982 950 <p>
983 951 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
984 952 </p>
@@ -1015,9 +983,9 @@
1015 983 'method': 'post',
1016 984 'action': ''
1017 985 });
1018 986
1019 - 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 ?>');
1020 988
1021 989 logoutForm.append($('<input>', {
1022 990 'type': 'hidden',
1023 991 'name': 'whitelabel_logout',
@@ -1089,9 +1057,9 @@
1089 1057 $.ajax({
1090 1058 url: ajaxUrl,
1091 1059 type: 'POST',
1092 1060 dataType: 'json',
1093 - 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')); ?>' },
1094 1062 timeout: 35000,
1095 1063 success: function(response, textStatus, xhr) {
1096 1064 try {
1097 1065 if (response && response.success && response.data) {
@@ -1159,22 +1127,22 @@
1159 1127 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1160 1128
1161 1129 html += '<div class="test-result-item ' + statusClass + '">';
1162 1130 html += '<div class="test-result-header">';
1163 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1131 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
1164 1132 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1165 1133 html += '</div>';
1166 1134
1167 1135 html += '<div class="test-result-details">';
1168 - html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
1169 - 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>';
1170 1138
1171 1139 if (result.status_code) {
1172 - 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>';
1173 1141 }
1174 1142
1175 1143 if (result.error) {
1176 - 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>';
1177 1145 }
1178 1146
1179 1147 if (result.body) {
1180 1148 html += '<p><strong>Response Body:</strong></p>';
@@ -1184,9 +1152,9 @@
1184 1152 if (result.headers && Object.keys(result.headers).length > 0) {
1185 1153 html += '<p><strong>Response Headers:</strong></p>';
1186 1154 html += '<pre class="response-headers">';
1187 1155 Object.keys(result.headers).forEach(function(key) {
1188 - html += key + ': ' + result.headers[key] + '\n';
1156 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
1189 1157 });
1190 1158 html += '</pre>';
1191 1159 }
1192 1160
@@ -1199,9 +1167,9 @@
1199 1167 html += '<p><strong>Parsed Response:</strong></p>';
1200 1168 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1201 1169 }
1202 1170
1203 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1171 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
1204 1172 html += '</div>';
1205 1173 html += '</div>';
1206 1174 });
1207 1175
@@ -1216,9 +1184,9 @@
1216 1184 '>': '&gt;',
1217 1185 '"': '&quot;',
1218 1186 '\'': '&#039;'
1219 1187 };
1220 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1188 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
1221 1189 }
1222 1190 });
1223 1191 </script>
1224 1192
@@ -1351,41 +1319,41 @@
1351 1319 }
1352 1320 </style>
1353 1321
1354 1322 <?php if ($active_tab === 'advanced'): ?>
1355 - <?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 ?>
1356 1324 <div class="notice notice-success is-dismissible">
1357 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>
1358 1326 </div>
1359 1327 <?php endif; ?>
1360 1328
1361 - <?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 ?>
1362 1330 <div class="notice notice-error is-dismissible">
1363 1331 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1364 1332 </div>
1365 1333 <?php endif; ?>
1366 1334
1367 - <?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 ?>
1368 1336 <div class="notice notice-success is-dismissible">
1369 1337 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1370 1338 </div>
1371 1339 <?php endif; ?>
1372 1340
1373 - <?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 ?>
1374 1342 <div class="notice notice-error is-dismissible">
1375 1343 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1376 1344 </div>
1377 1345 <?php endif; ?>
1378 1346
1379 - <?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 ?>
1380 1348 <div class="notice notice-success is-dismissible">
1381 1349 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1382 1350 </div>
1383 1351 <?php endif; ?>
1384 1352
1385 - <?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 ?>
1386 1354 <div class="notice notice-error is-dismissible">
1387 - <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>
1388 1356 </div>
1389 1357 <?php endif; ?>
1390 1358
1391 1359 <?php endif; ?>
@@ -1407,9 +1375,9 @@
1407 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>
1408 1376 <?php
1409 1377 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1410 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>';
1411 - 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>';
1412 1380 } else {
1413 1381 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>';
1414 1382 }
1415 1383 ?>
@@ -1419,8 +1387,15 @@
1419 1387 }
1420 1388
1421 1389 public function create_admin_robots_txt_page()
1422 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 +
1423 1398 ?>
1424 1399 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1425 1400
1426 1401 <?php
@@ -1441,8 +1416,11 @@
1441 1416
1442 1417 if (is_wp_error($result)) {
1443 1418 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1444 1419 } else {
1420 + Metasync_PostHog::feature('robots_txt_saved', array(
1421 + 'warning_count' => count($validation['warnings']),
1422 + ));
1445 1423 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1446 1424
1447 1425 if (!empty($validation['warnings'])) {
1448 1426 foreach ($validation['warnings'] as $warning) {
@@ -1483,82 +1461,8 @@
1483 1461 {
1484 1462 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1485 1463 }
1486 1464
1487 - public function create_admin_xml_sitemap_page()
1488 - {
1489 - require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1490 -
1491 - $sitemap_generator = new Metasync_Sitemap_Generator();
1492 -
1493 - if (isset($_POST['metasync_sitemap_nonce'])) {
1494 - check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1495 -
1496 - if (isset($_POST['generate_sitemap'])) {
1497 - $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1498 -
1499 - $result = $sitemap_generator->generate_sitemap();
1500 -
1501 - if (is_wp_error($result)) {
1502 - error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1503 - echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1504 - } else {
1505 - $message = esc_html__('Sitemap generated successfully!', 'metasync');
1506 - if ($disabled_plugins) {
1507 - $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1508 - }
1509 -
1510 - $robots_result = get_transient('metasync_sitemap_robots_updated');
1511 - if ($robots_result && $robots_result['success']) {
1512 - if ($robots_result['action'] === 'added') {
1513 - $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1514 - } elseif ($robots_result['action'] === 'updated') {
1515 - $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1516 - } elseif ($robots_result['action'] === 'created') {
1517 - $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1518 - }
1519 - delete_transient('metasync_sitemap_robots_updated');
1520 - }
1521 -
1522 - echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1523 - }
1524 - } elseif (isset($_POST['enable_auto_update'])) {
1525 - update_option('metasync_sitemap_auto_update', true);
1526 - $sitemap_generator->setup_auto_update_hooks();
1527 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1528 - } elseif (isset($_POST['disable_auto_update'])) {
1529 - update_option('metasync_sitemap_auto_update', false);
1530 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1531 - } elseif (isset($_POST['delete_sitemap'])) {
1532 - $deleted = $sitemap_generator->delete_sitemap();
1533 - if ($deleted) {
1534 - update_option('metasync_sitemap_auto_update', false);
1535 - // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1536 - delete_option('metasync_disable_wp_sitemap');
1537 - echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1538 - } else {
1539 - 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>';
1540 - }
1541 - } elseif (isset($_POST['enable_other_sitemaps'])) {
1542 - $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1543 - if ($enabled_plugins) {
1544 - echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1545 - } else {
1546 - echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1547 - }
1548 - }
1549 - }
1550 -
1551 - $sitemap_exists = $sitemap_generator->sitemap_exists();
1552 - $sitemap_url = $sitemap_generator->get_sitemap_url();
1553 - $url_count = $sitemap_generator->count_urls();
1554 - $last_generated = $sitemap_generator->get_last_generated_time();
1555 - $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1556 - $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1557 -
1558 - require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1559 - }
1560 -
1561 1465 public function create_admin_custom_pages_page()
1562 1466 {
1563 1467 ?>
1564 1468 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
@@ -1609,12 +1513,14 @@
1609 1513 }
1610 1514
1611 1515 public function create_admin_breadcrumbs_page()
1612 1516 {
1613 - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1614 1517 ?>
1615 1518 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1616 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 +
1617 1523 <form method="post" action="options.php">
1618 1524 <div class="dashboard-card">
1619 1525 <h2>Breadcrumbs Settings</h2>
1620 1526 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
@@ -1619,9 +1525,9 @@
1619 1525 <h2>Breadcrumbs Settings</h2>
1620 1526 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1621 1527 <?php
1622 1528 settings_fields(Metasync_Admin::option_group);
1623 - do_settings_sections($page_slug);
1529 + Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1624 1530 submit_button();
1625 1531 ?>
1626 1532 </div>
1627 1533 </form>
@@ -1741,14 +1647,8 @@
1741 1647 </div>
1742 1648 </form>
1743 1649 <?php $this->admin->render_layout_close(); ?>
1744 1650 <?php
1745 - }
1746 -
1747 - public function create_admin_google_instant_index_page()
1748 - {
1749 - // Delegates to the admin class which uses the shared credentials view
1750 - $this->admin->create_admin_google_instant_index_page();
1751 1651 }
1752 1652
1753 1653 public function create_admin_google_console_page()
1754 1654 {