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-pages.php +42 -161 2.6.24 → 2.7.1 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
@@ -344,63 +349,8 @@
344 349 <?php endif; ?>
345 350
346 351 </div>
347 352
348 - <script>
349 - jQuery(document).ready(function($) {
350 - $('#whitelabel_password').focus();
351 -
352 - $('#whitelabel_password').on('keypress', function(e) {
353 - if (e.which === 13) {
354 - $(this).closest('form').submit();
355 - }
356 - });
357 -
358 - <?php if (current_user_can('manage_options')): ?>
359 - $('#metasync-forgot-password-link').on('click', function(e) {
360 - e.preventDefault();
361 -
362 - var $link = $(this);
363 - var $message = $('#metasync-recovery-message');
364 -
365 - $link.css('pointer-events', 'none').css('opacity', '0.6');
366 - $message.removeClass('success error').hide();
367 - $message.html('⏳ Sending recovery email...').css('background', '#f0f6fc').css('color', '#0c5ba5').css('border', '1px solid #cfe2f3').fadeIn(200);
368 -
369 - $.ajax({
370 - url: ajaxurl,
371 - type: 'POST',
372 - data: {
373 - action: 'metasync_recover_password',
374 - nonce: '<?php echo wp_create_nonce('metasync_recover_password_nonce'); ?>'
375 - },
376 - success: function(response) {
377 - $link.css('pointer-events', 'auto').css('opacity', '1');
378 -
379 - if (response.success) {
380 - $message.addClass('success').html('✅ ' + response.data.message)
381 - .css('background', '#d4edda')
382 - .css('color', '#155724')
383 - .css('border', '1px solid #c3e6cb');
384 - } else {
385 - $message.addClass('error').html('❌ ' + response.data.message)
386 - .css('background', '#f8d7da')
387 - .css('color', '#721c24')
388 - .css('border', '1px solid #f5c6cb');
389 - }
390 - },
391 - error: function() {
392 - $link.css('pointer-events', 'auto').css('opacity', '1');
393 - $message.addClass('error').html('❌ An error occurred. Please try again.')
394 - .css('background', '#f8d7da')
395 - .css('color', '#721c24')
396 - .css('border', '1px solid #f5c6cb');
397 - }
398 - });
399 - });
400 - <?php endif; ?>
401 - });
402 - </script>
403 353 <?php
404 354 return;
405 355 } elseif (!$password_protection_enabled) {
406 356 ?>
@@ -414,8 +364,9 @@
414 364 }
415 365 ?>
416 366
417 367 <form method="post" action="options.php?tab=<?php echo esc_attr( $active_tab ); ?>" id="metaSyncGeneralSetting">
368 + <input type="hidden" name="metasync_pw_reset_generation" value="<?php echo esc_attr((string) (int) get_option('metasync_pw_reset_generation', 0)); ?>" />
418 369 <?php
419 370 settings_fields(Metasync_Admin::option_group);
420 371
421 372 wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce');
@@ -443,8 +394,11 @@
443 394
444 395 <?php
445 396 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
446 397 ?>
398 + <p style="margin: 20px 0 0 0; font-size: 13px; color: var(--dashboard-text-secondary);">
399 + 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>.
400 + </p>
447 401 </div>
448 402
449 403 <div class="dashboard-card">
450 404 <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>
@@ -954,10 +908,10 @@
954 908 </div>
955 909 <?php endif; ?>
956 910
957 911 <?php
958 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
959 - $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
912 + 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
913 + $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice
960 914 ?>
961 915 <div class="notice notice-success inline" style="margin-bottom: 20px;">
962 916 <p>
963 917 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -964,9 +918,9 @@
964 918 </p>
965 919 </div>
966 920 <?php endif; ?>
967 921
968 - <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?>
922 + <?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 ?>
969 923 <div class="notice notice-info inline" style="margin-bottom: 20px;">
970 924 <p>
971 925 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
972 926 </p>
@@ -972,9 +926,9 @@
972 926 </p>
973 927 </div>
974 928 <?php endif; ?>
975 929
976 - <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?>
930 + <?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 ?>
977 931 <div class="notice notice-success inline" style="margin-bottom: 20px;">
978 932 <p>
979 933 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
980 934 </p>
@@ -980,9 +934,9 @@
980 934 </p>
981 935 </div>
982 936 <?php endif; ?>
983 937
984 - <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?>
938 + <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
985 939 <div class="notice notice-error inline" style="margin-bottom: 20px;">
986 940 <p>
987 941 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
988 942 </p>
@@ -1163,22 +1117,22 @@
1163 1117 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1164 1118
1165 1119 html += '<div class="test-result-item ' + statusClass + '">';
1166 1120 html += '<div class="test-result-header">';
1167 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1121 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
1168 1122 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1169 1123 html += '</div>';
1170 1124
1171 1125 html += '<div class="test-result-details">';
1172 - html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
1173 - html += '<p><strong>Status:</strong> ' + result.status + '</p>';
1126 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
1127 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
1174 1128
1175 1129 if (result.status_code) {
1176 - html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
1130 + html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + escapeHtml(result.status_code) + '</span></p>';
1177 1131 }
1178 1132
1179 1133 if (result.error) {
1180 - html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
1134 + html += '<p><strong>Error:</strong> <span class="error-message">' + escapeHtml(result.error) + '</span></p>';
1181 1135 }
1182 1136
1183 1137 if (result.body) {
1184 1138 html += '<p><strong>Response Body:</strong></p>';
@@ -1188,9 +1142,9 @@
1188 1142 if (result.headers && Object.keys(result.headers).length > 0) {
1189 1143 html += '<p><strong>Response Headers:</strong></p>';
1190 1144 html += '<pre class="response-headers">';
1191 1145 Object.keys(result.headers).forEach(function(key) {
1192 - html += key + ': ' + result.headers[key] + '\n';
1146 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
1193 1147 });
1194 1148 html += '</pre>';
1195 1149 }
1196 1150
@@ -1203,9 +1157,9 @@
1203 1157 html += '<p><strong>Parsed Response:</strong></p>';
1204 1158 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1205 1159 }
1206 1160
1207 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1161 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
1208 1162 html += '</div>';
1209 1163 html += '</div>';
1210 1164 });
1211 1165
@@ -1220,9 +1174,9 @@
1220 1174 '>': '&gt;',
1221 1175 '"': '&quot;',
1222 1176 '\'': '&#039;'
1223 1177 };
1224 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1178 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
1225 1179 }
1226 1180 });
1227 1181 </script>
1228 1182
@@ -1355,41 +1309,41 @@
1355 1309 }
1356 1310 </style>
1357 1311
1358 1312 <?php if ($active_tab === 'advanced'): ?>
1359 - <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?>
1313 + <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1360 1314 <div class="notice notice-success is-dismissible">
1361 1315 <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>
1362 1316 </div>
1363 1317 <?php endif; ?>
1364 1318
1365 - <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?>
1319 + <?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 ?>
1366 1320 <div class="notice notice-error is-dismissible">
1367 1321 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1368 1322 </div>
1369 1323 <?php endif; ?>
1370 1324
1371 - <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?>
1325 + <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1372 1326 <div class="notice notice-success is-dismissible">
1373 1327 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1374 1328 </div>
1375 1329 <?php endif; ?>
1376 1330
1377 - <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?>
1331 + <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for a static notice ?>
1378 1332 <div class="notice notice-error is-dismissible">
1379 1333 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1380 1334 </div>
1381 1335 <?php endif; ?>
1382 1336
1383 - <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?>
1337 + <?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 ?>
1384 1338 <div class="notice notice-success is-dismissible">
1385 1339 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1386 1340 </div>
1387 1341 <?php endif; ?>
1388 1342
1389 - <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?>
1343 + <?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 ?>
1390 1344 <div class="notice notice-error is-dismissible">
1391 - <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p>
1345 + <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>
1392 1346 </div>
1393 1347 <?php endif; ?>
1394 1348
1395 1349 <?php endif; ?>
@@ -1423,8 +1377,15 @@
1423 1377 }
1424 1378
1425 1379 public function create_admin_robots_txt_page()
1426 1380 {
1381 + // The form below rewrites the site's physical robots.txt — restrict
1382 + // that to site admins, not just anyone who can open the page via
1383 + // plugin_access_roles. The nonce is CSRF protection, not authorization.
1384 + if (!empty($_POST) && !current_user_can('manage_options')) {
1385 + wp_die(esc_html__('Sorry, you are not allowed to manage robots.txt settings.', 'metasync'));
1386 + }
1387 +
1427 1388 ?>
1428 1389 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1429 1390
1430 1391 <?php
@@ -1487,84 +1448,8 @@
1487 1448 {
1488 1449 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1489 1450 }
1490 1451
1491 - public function create_admin_xml_sitemap_page()
1492 - {
1493 - require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1494 -
1495 - $sitemap_generator = new Metasync_Sitemap_Generator();
1496 -
1497 - if (isset($_POST['metasync_sitemap_nonce'])) {
1498 - check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1499 -
1500 - if (isset($_POST['generate_sitemap'])) {
1501 - $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1502 -
1503 - $result = $sitemap_generator->generate_sitemap();
1504 -
1505 - if (is_wp_error($result)) {
1506 - error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1507 - echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1508 - } elseif (false === $result) {
1509 - 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>';
1510 - } else {
1511 - $message = esc_html__('Sitemap generated successfully!', 'metasync');
1512 - if ($disabled_plugins) {
1513 - $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1514 - }
1515 -
1516 - $robots_result = get_transient('metasync_sitemap_robots_updated');
1517 - if ($robots_result && $robots_result['success']) {
1518 - if ($robots_result['action'] === 'added') {
1519 - $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1520 - } elseif ($robots_result['action'] === 'updated') {
1521 - $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1522 - } elseif ($robots_result['action'] === 'created') {
1523 - $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1524 - }
1525 - delete_transient('metasync_sitemap_robots_updated');
1526 - }
1527 -
1528 - echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1529 - }
1530 - } elseif (isset($_POST['enable_auto_update'])) {
1531 - update_option('metasync_sitemap_auto_update', true);
1532 - $sitemap_generator->setup_auto_update_hooks();
1533 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1534 - } elseif (isset($_POST['disable_auto_update'])) {
1535 - update_option('metasync_sitemap_auto_update', false);
1536 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1537 - } elseif (isset($_POST['delete_sitemap'])) {
1538 - $deleted = $sitemap_generator->delete_sitemap();
1539 - if ($deleted) {
1540 - update_option('metasync_sitemap_auto_update', false);
1541 - // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1542 - delete_option('metasync_disable_wp_sitemap');
1543 - echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1544 - } else {
1545 - 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>';
1546 - }
1547 - } elseif (isset($_POST['enable_other_sitemaps'])) {
1548 - $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1549 - if ($enabled_plugins) {
1550 - echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1551 - } else {
1552 - echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1553 - }
1554 - }
1555 - }
1556 -
1557 - $sitemap_exists = $sitemap_generator->sitemap_exists();
1558 - $sitemap_url = $sitemap_generator->get_sitemap_url();
1559 - $url_count = $sitemap_generator->count_urls();
1560 - $last_generated = $sitemap_generator->get_last_generated_time();
1561 - $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1562 - $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1563 -
1564 - require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1565 - }
1566 -
1567 1452 public function create_admin_custom_pages_page()
1568 1453 {
1569 1454 ?>
1570 1455 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
@@ -1615,12 +1500,14 @@
1615 1500 }
1616 1501
1617 1502 public function create_admin_breadcrumbs_page()
1618 1503 {
1619 - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1620 1504 ?>
1621 1505 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1622 1506
1507 + <?php /* options.php redirects back here with ?settings-updated=true; without this call the save confirmation never prints */ ?>
1508 + <?php settings_errors(); ?>
1509 +
1623 1510 <form method="post" action="options.php">
1624 1511 <div class="dashboard-card">
1625 1512 <h2>Breadcrumbs Settings</h2>
1626 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
@@ -1625,9 +1512,9 @@
1625 1512 <h2>Breadcrumbs Settings</h2>
1626 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1627 1514 <?php
1628 1515 settings_fields(Metasync_Admin::option_group);
1629 - do_settings_sections($page_slug);
1516 + Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1630 1517 submit_button();
1631 1518 ?>
1632 1519 </div>
1633 1520 </form>
@@ -1747,14 +1634,8 @@
1747 1634 </div>
1748 1635 </form>
1749 1636 <?php $this->admin->render_layout_close(); ?>
1750 1637 <?php
1751 - }
1752 -
1753 - public function create_admin_google_instant_index_page()
1754 - {
1755 - // Delegates to the admin class which uses the shared credentials view
1756 - $this->admin->create_admin_google_instant_index_page();
1757 1638 }
1758 1639
1759 1640 public function create_admin_google_console_page()
1760 1641 {