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 +44 -157 2.6.22 → 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
@@ -333,8 +338,9 @@
333 338 </button>
334 339 </div>
335 340 </form>
336 341
342 + <?php if (current_user_can('manage_options')): ?>
337 343 <div style="text-align: center; margin-top: 20px;">
338 344 <a href="#" id="metasync-forgot-password-link" style="color: #2271b1; text-decoration: none; font-size: 14px;">
339 345 Forgot Password?
340 346 </a>
@@ -339,64 +345,12 @@
339 345 Forgot Password?
340 346 </a>
341 347 <div id="metasync-recovery-message" style="margin-top: 15px; padding: 12px; border-radius: 6px; display: none;"></div>
342 348 </div>
349 + <?php endif; ?>
343 350
344 351 </div>
345 352
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 353 <?php
400 354 return;
401 355 } elseif (!$password_protection_enabled) {
402 356 ?>
@@ -410,8 +364,9 @@
410 364 }
411 365 ?>
412 366
413 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)); ?>" />
414 369 <?php
415 370 settings_fields(Metasync_Admin::option_group);
416 371
417 372 wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce');
@@ -439,8 +394,11 @@
439 394
440 395 <?php
441 396 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
442 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>
443 401 </div>
444 402
445 403 <div class="dashboard-card">
446 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>
@@ -950,10 +908,10 @@
950 908 </div>
951 909 <?php endif; ?>
952 910
953 911 <?php
954 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
955 - $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
956 914 ?>
957 915 <div class="notice notice-success inline" style="margin-bottom: 20px;">
958 916 <p>
959 917 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -960,9 +918,9 @@
960 918 </p>
961 919 </div>
962 920 <?php endif; ?>
963 921
964 - <?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 ?>
965 923 <div class="notice notice-info inline" style="margin-bottom: 20px;">
966 924 <p>
967 925 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
968 926 </p>
@@ -968,9 +926,9 @@
968 926 </p>
969 927 </div>
970 928 <?php endif; ?>
971 929
972 - <?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 ?>
973 931 <div class="notice notice-success inline" style="margin-bottom: 20px;">
974 932 <p>
975 933 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
976 934 </p>
@@ -976,9 +934,9 @@
976 934 </p>
977 935 </div>
978 936 <?php endif; ?>
979 937
980 - <?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 ?>
981 939 <div class="notice notice-error inline" style="margin-bottom: 20px;">
982 940 <p>
983 941 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
984 942 </p>
@@ -1159,22 +1117,22 @@
1159 1117 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1160 1118
1161 1119 html += '<div class="test-result-item ' + statusClass + '">';
1162 1120 html += '<div class="test-result-header">';
1163 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1121 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
1164 1122 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1165 1123 html += '</div>';
1166 1124
1167 1125 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>';
1126 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
1127 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
1170 1128
1171 1129 if (result.status_code) {
1172 - 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>';
1173 1131 }
1174 1132
1175 1133 if (result.error) {
1176 - 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>';
1177 1135 }
1178 1136
1179 1137 if (result.body) {
1180 1138 html += '<p><strong>Response Body:</strong></p>';
@@ -1184,9 +1142,9 @@
1184 1142 if (result.headers && Object.keys(result.headers).length > 0) {
1185 1143 html += '<p><strong>Response Headers:</strong></p>';
1186 1144 html += '<pre class="response-headers">';
1187 1145 Object.keys(result.headers).forEach(function(key) {
1188 - html += key + ': ' + result.headers[key] + '\n';
1146 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
1189 1147 });
1190 1148 html += '</pre>';
1191 1149 }
1192 1150
@@ -1199,9 +1157,9 @@
1199 1157 html += '<p><strong>Parsed Response:</strong></p>';
1200 1158 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1201 1159 }
1202 1160
1203 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1161 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
1204 1162 html += '</div>';
1205 1163 html += '</div>';
1206 1164 });
1207 1165
@@ -1216,9 +1174,9 @@
1216 1174 '>': '&gt;',
1217 1175 '"': '&quot;',
1218 1176 '\'': '&#039;'
1219 1177 };
1220 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1178 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
1221 1179 }
1222 1180 });
1223 1181 </script>
1224 1182
@@ -1351,41 +1309,41 @@
1351 1309 }
1352 1310 </style>
1353 1311
1354 1312 <?php if ($active_tab === 'advanced'): ?>
1355 - <?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 ?>
1356 1314 <div class="notice notice-success is-dismissible">
1357 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>
1358 1316 </div>
1359 1317 <?php endif; ?>
1360 1318
1361 - <?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 ?>
1362 1320 <div class="notice notice-error is-dismissible">
1363 1321 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1364 1322 </div>
1365 1323 <?php endif; ?>
1366 1324
1367 - <?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 ?>
1368 1326 <div class="notice notice-success is-dismissible">
1369 1327 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1370 1328 </div>
1371 1329 <?php endif; ?>
1372 1330
1373 - <?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 ?>
1374 1332 <div class="notice notice-error is-dismissible">
1375 1333 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1376 1334 </div>
1377 1335 <?php endif; ?>
1378 1336
1379 - <?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 ?>
1380 1338 <div class="notice notice-success is-dismissible">
1381 1339 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1382 1340 </div>
1383 1341 <?php endif; ?>
1384 1342
1385 - <?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 ?>
1386 1344 <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>
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>
1388 1346 </div>
1389 1347 <?php endif; ?>
1390 1348
1391 1349 <?php endif; ?>
@@ -1419,8 +1377,15 @@
1419 1377 }
1420 1378
1421 1379 public function create_admin_robots_txt_page()
1422 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 +
1423 1388 ?>
1424 1389 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1425 1390
1426 1391 <?php
@@ -1483,82 +1448,8 @@
1483 1448 {
1484 1449 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1485 1450 }
1486 1451
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 1452 public function create_admin_custom_pages_page()
1562 1453 {
1563 1454 ?>
1564 1455 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
@@ -1609,12 +1500,14 @@
1609 1500 }
1610 1501
1611 1502 public function create_admin_breadcrumbs_page()
1612 1503 {
1613 - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1614 1504 ?>
1615 1505 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1616 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 +
1617 1510 <form method="post" action="options.php">
1618 1511 <div class="dashboard-card">
1619 1512 <h2>Breadcrumbs Settings</h2>
1620 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
@@ -1619,9 +1512,9 @@
1619 1512 <h2>Breadcrumbs Settings</h2>
1620 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1621 1514 <?php
1622 1515 settings_fields(Metasync_Admin::option_group);
1623 - do_settings_sections($page_slug);
1516 + Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1624 1517 submit_button();
1625 1518 ?>
1626 1519 </div>
1627 1520 </form>
@@ -1741,14 +1634,8 @@
1741 1634 </div>
1742 1635 </form>
1743 1636 <?php $this->admin->render_layout_close(); ?>
1744 1637 <?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 1638 }
1752 1639
1753 1640 public function create_admin_google_console_page()
1754 1641 {