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 +43 -99 2.7.0 → 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>
@@ -783,21 +793,21 @@
783 793 }
784 794 });
785 795
786 796 function hasPassword() {
787 - 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]"]');
788 798 var passwordValue = passwordField.val();
789 799 return passwordValue && passwordValue.length > 0;
790 800 }
791 801
792 802 function hasRecoveryEmail() {
793 - 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]"]');
794 804 var emailValue = recoveryEmailField.val();
795 805 return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue);
796 806 }
797 807
798 - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
799 - 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]"]');
800 810
801 811 passwordField.on('blur', function() {
802 812 if (hasPassword() && !hasRecoveryEmail()) {
803 813 showModal(
@@ -835,9 +845,9 @@
835 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.'
836 846 );
837 847
838 848 setTimeout(function() {
839 - $('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();
840 850 }, 300);
841 851
842 852 return false;
843 853 }
@@ -908,10 +918,10 @@
908 918 </div>
909 919 <?php endif; ?>
910 920
911 921 <?php
912 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
913 - $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
914 924 ?>
915 925 <div class="notice notice-success inline" style="margin-bottom: 20px;">
916 926 <p>
917 927 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -918,9 +928,9 @@
918 928 </p>
919 929 </div>
920 930 <?php endif; ?>
921 931
922 - <?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 ?>
923 933 <div class="notice notice-info inline" style="margin-bottom: 20px;">
924 934 <p>
925 935 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
926 936 </p>
@@ -926,9 +936,9 @@
926 936 </p>
927 937 </div>
928 938 <?php endif; ?>
929 939
930 - <?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 ?>
931 941 <div class="notice notice-success inline" style="margin-bottom: 20px;">
932 942 <p>
933 943 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
934 944 </p>
@@ -934,9 +944,9 @@
934 944 </p>
935 945 </div>
936 946 <?php endif; ?>
937 947
938 - <?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 ?>
939 949 <div class="notice notice-error inline" style="margin-bottom: 20px;">
940 950 <p>
941 951 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
942 952 </p>
@@ -973,9 +983,9 @@
973 983 'method': 'post',
974 984 'action': ''
975 985 });
976 986
977 - 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 ?>');
978 988
979 989 logoutForm.append($('<input>', {
980 990 'type': 'hidden',
981 991 'name': 'whitelabel_logout',
@@ -1047,9 +1057,9 @@
1047 1057 $.ajax({
1048 1058 url: ajaxUrl,
1049 1059 type: 'POST',
1050 1060 dataType: 'json',
1051 - 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')); ?>' },
1052 1062 timeout: 35000,
1053 1063 success: function(response, textStatus, xhr) {
1054 1064 try {
1055 1065 if (response && response.success && response.data) {
@@ -1309,41 +1319,41 @@
1309 1319 }
1310 1320 </style>
1311 1321
1312 1322 <?php if ($active_tab === 'advanced'): ?>
1313 - <?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 ?>
1314 1324 <div class="notice notice-success is-dismissible">
1315 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>
1316 1326 </div>
1317 1327 <?php endif; ?>
1318 1328
1319 - <?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 ?>
1320 1330 <div class="notice notice-error is-dismissible">
1321 1331 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1322 1332 </div>
1323 1333 <?php endif; ?>
1324 1334
1325 - <?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 ?>
1326 1336 <div class="notice notice-success is-dismissible">
1327 1337 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1328 1338 </div>
1329 1339 <?php endif; ?>
1330 1340
1331 - <?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 ?>
1332 1342 <div class="notice notice-error is-dismissible">
1333 1343 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1334 1344 </div>
1335 1345 <?php endif; ?>
1336 1346
1337 - <?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 ?>
1338 1348 <div class="notice notice-success is-dismissible">
1339 1349 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1340 1350 </div>
1341 1351 <?php endif; ?>
1342 1352
1343 - <?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 ?>
1344 1354 <div class="notice notice-error is-dismissible">
1345 - <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>
1346 1356 </div>
1347 1357 <?php endif; ?>
1348 1358
1349 1359 <?php endif; ?>
@@ -1365,9 +1375,9 @@
1365 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>
1366 1376 <?php
1367 1377 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1368 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>';
1369 - 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>';
1370 1380 } else {
1371 1381 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>';
1372 1382 }
1373 1383 ?>
@@ -1377,8 +1387,15 @@
1377 1387 }
1378 1388
1379 1389 public function create_admin_robots_txt_page()
1380 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 +
1381 1398 ?>
1382 1399 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1383 1400
1384 1401 <?php
@@ -1399,8 +1416,11 @@
1399 1416
1400 1417 if (is_wp_error($result)) {
1401 1418 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1402 1419 } else {
1420 + Metasync_PostHog::feature('robots_txt_saved', array(
1421 + 'warning_count' => count($validation['warnings']),
1422 + ));
1403 1423 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1404 1424
1405 1425 if (!empty($validation['warnings'])) {
1406 1426 foreach ($validation['warnings'] as $warning) {
@@ -1439,84 +1459,8 @@
1439 1459
1440 1460 public function create_admin_report_issue_page()
1441 1461 {
1442 1462 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1443 - }
1444 -
1445 - public function create_admin_xml_sitemap_page()
1446 - {
1447 - require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1448 -
1449 - $sitemap_generator = new Metasync_Sitemap_Generator();
1450 -
1451 - if (isset($_POST['metasync_sitemap_nonce'])) {
1452 - check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1453 -
1454 - if (isset($_POST['generate_sitemap'])) {
1455 - $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1456 -
1457 - $result = $sitemap_generator->generate_sitemap();
1458 -
1459 - if (is_wp_error($result)) {
1460 - error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1461 - echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1462 - } elseif (false === $result) {
1463 - 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>';
1464 - } else {
1465 - $message = esc_html__('Sitemap generated successfully!', 'metasync');
1466 - if ($disabled_plugins) {
1467 - $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1468 - }
1469 -
1470 - $robots_result = get_transient('metasync_sitemap_robots_updated');
1471 - if ($robots_result && $robots_result['success']) {
1472 - if ($robots_result['action'] === 'added') {
1473 - $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1474 - } elseif ($robots_result['action'] === 'updated') {
1475 - $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1476 - } elseif ($robots_result['action'] === 'created') {
1477 - $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1478 - }
1479 - delete_transient('metasync_sitemap_robots_updated');
1480 - }
1481 -
1482 - echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1483 - }
1484 - } elseif (isset($_POST['enable_auto_update'])) {
1485 - update_option('metasync_sitemap_auto_update', true);
1486 - $sitemap_generator->setup_auto_update_hooks();
1487 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1488 - } elseif (isset($_POST['disable_auto_update'])) {
1489 - update_option('metasync_sitemap_auto_update', false);
1490 - echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1491 - } elseif (isset($_POST['delete_sitemap'])) {
1492 - $deleted = $sitemap_generator->delete_sitemap();
1493 - if ($deleted) {
1494 - update_option('metasync_sitemap_auto_update', false);
1495 - // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1496 - delete_option('metasync_disable_wp_sitemap');
1497 - echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1498 - } else {
1499 - 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>';
1500 - }
1501 - } elseif (isset($_POST['enable_other_sitemaps'])) {
1502 - $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1503 - if ($enabled_plugins) {
1504 - echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1505 - } else {
1506 - echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1507 - }
1508 - }
1509 - }
1510 -
1511 - $sitemap_exists = $sitemap_generator->sitemap_exists();
1512 - $sitemap_url = $sitemap_generator->get_sitemap_url();
1513 - $url_count = $sitemap_generator->count_urls();
1514 - $last_generated = $sitemap_generator->get_last_generated_time();
1515 - $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1516 - $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1517 -
1518 - require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1519 1463 }
1520 1464
1521 1465 public function create_admin_custom_pages_page()
1522 1466 {