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 +37 -107 2.6.25 → 2.7.1 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
@@ -394,8 +394,11 @@
394 394
395 395 <?php
396 396 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
397 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>
398 401 </div>
399 402
400 403 <div class="dashboard-card">
401 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>
@@ -905,10 +908,10 @@
905 908 </div>
906 909 <?php endif; ?>
907 910
908 911 <?php
909 - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
910 - $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
911 914 ?>
912 915 <div class="notice notice-success inline" style="margin-bottom: 20px;">
913 916 <p>
914 917 <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
@@ -915,9 +918,9 @@
915 918 </p>
916 919 </div>
917 920 <?php endif; ?>
918 921
919 - <?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 ?>
920 923 <div class="notice notice-info inline" style="margin-bottom: 20px;">
921 924 <p>
922 925 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
923 926 </p>
@@ -923,9 +926,9 @@
923 926 </p>
924 927 </div>
925 928 <?php endif; ?>
926 929
927 - <?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 ?>
928 931 <div class="notice notice-success inline" style="margin-bottom: 20px;">
929 932 <p>
930 933 <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours.
931 934 </p>
@@ -931,9 +934,9 @@
931 934 </p>
932 935 </div>
933 936 <?php endif; ?>
934 937
935 - <?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 ?>
936 939 <div class="notice notice-error inline" style="margin-bottom: 20px;">
937 940 <p>
938 941 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
939 942 </p>
@@ -1114,22 +1117,22 @@
1114 1117 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1115 1118
1116 1119 html += '<div class="test-result-item ' + statusClass + '">';
1117 1120 html += '<div class="test-result-header">';
1118 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1121 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
1119 1122 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1120 1123 html += '</div>';
1121 1124
1122 1125 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>';
1126 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
1127 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
1125 1128
1126 1129 if (result.status_code) {
1127 - 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>';
1128 1131 }
1129 1132
1130 1133 if (result.error) {
1131 - 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>';
1132 1135 }
1133 1136
1134 1137 if (result.body) {
1135 1138 html += '<p><strong>Response Body:</strong></p>';
@@ -1139,9 +1142,9 @@
1139 1142 if (result.headers && Object.keys(result.headers).length > 0) {
1140 1143 html += '<p><strong>Response Headers:</strong></p>';
1141 1144 html += '<pre class="response-headers">';
1142 1145 Object.keys(result.headers).forEach(function(key) {
1143 - html += key + ': ' + result.headers[key] + '\n';
1146 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
1144 1147 });
1145 1148 html += '</pre>';
1146 1149 }
1147 1150
@@ -1154,9 +1157,9 @@
1154 1157 html += '<p><strong>Parsed Response:</strong></p>';
1155 1158 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1156 1159 }
1157 1160
1158 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1161 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
1159 1162 html += '</div>';
1160 1163 html += '</div>';
1161 1164 });
1162 1165
@@ -1171,9 +1174,9 @@
1171 1174 '>': '&gt;',
1172 1175 '"': '&quot;',
1173 1176 '\'': '&#039;'
1174 1177 };
1175 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1178 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
1176 1179 }
1177 1180 });
1178 1181 </script>
1179 1182
@@ -1306,41 +1309,41 @@
1306 1309 }
1307 1310 </style>
1308 1311
1309 1312 <?php if ($active_tab === 'advanced'): ?>
1310 - <?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 ?>
1311 1314 <div class="notice notice-success is-dismissible">
1312 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>
1313 1316 </div>
1314 1317 <?php endif; ?>
1315 1318
1316 - <?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 ?>
1317 1320 <div class="notice notice-error is-dismissible">
1318 1321 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1319 1322 </div>
1320 1323 <?php endif; ?>
1321 1324
1322 - <?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 ?>
1323 1326 <div class="notice notice-success is-dismissible">
1324 1327 <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p>
1325 1328 </div>
1326 1329 <?php endif; ?>
1327 1330
1328 - <?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 ?>
1329 1332 <div class="notice notice-error is-dismissible">
1330 1333 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1331 1334 </div>
1332 1335 <?php endif; ?>
1333 1336
1334 - <?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 ?>
1335 1338 <div class="notice notice-success is-dismissible">
1336 1339 <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p>
1337 1340 </div>
1338 1341 <?php endif; ?>
1339 1342
1340 - <?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 ?>
1341 1344 <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>
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>
1343 1346 </div>
1344 1347 <?php endif; ?>
1345 1348
1346 1349 <?php endif; ?>
@@ -1374,8 +1377,15 @@
1374 1377 }
1375 1378
1376 1379 public function create_admin_robots_txt_page()
1377 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 +
1378 1388 ?>
1379 1389 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1380 1390
1381 1391 <?php
@@ -1438,84 +1448,8 @@
1438 1448 {
1439 1449 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1440 1450 }
1441 1451
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 1452 public function create_admin_custom_pages_page()
1519 1453 {
1520 1454 ?>
1521 1455 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
@@ -1566,12 +1500,14 @@
1566 1500 }
1567 1501
1568 1502 public function create_admin_breadcrumbs_page()
1569 1503 {
1570 - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1571 1504 ?>
1572 1505 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1573 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 +
1574 1510 <form method="post" action="options.php">
1575 1511 <div class="dashboard-card">
1576 1512 <h2>Breadcrumbs Settings</h2>
1577 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
@@ -1576,9 +1512,9 @@
1576 1512 <h2>Breadcrumbs Settings</h2>
1577 1513 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1578 1514 <?php
1579 1515 settings_fields(Metasync_Admin::option_group);
1580 - do_settings_sections($page_slug);
1516 + Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1581 1517 submit_button();
1582 1518 ?>
1583 1519 </div>
1584 1520 </form>
@@ -1698,14 +1634,8 @@
1698 1634 </div>
1699 1635 </form>
1700 1636 <?php $this->admin->render_layout_close(); ?>
1701 1637 <?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 1638 }
1709 1639
1710 1640 public function create_admin_google_console_page()
1711 1641 {