| @@ -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> |
| @@ -344,63 +359,8 @@ | ||
| 344 | 359 | <?php endif; ?> |
| 345 | 360 | |
| 346 | 361 | </div> |
| 347 | 362 | |
| 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 | 363 | <?php |
| 404 | 364 | return; |
| 405 | 365 | } elseif (!$password_protection_enabled) { |
| 406 | 366 | ?> |
| @@ -414,8 +374,9 @@ | ||
| 414 | 374 | } |
| 415 | 375 | ?> |
| 416 | 376 | |
| 417 | 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)); ?>" /> | |
| 418 | 379 | <?php |
| 419 | 380 | settings_fields(Metasync_Admin::option_group); |
| 420 | 381 | |
| 421 | 382 | wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce'); |
| @@ -443,8 +404,11 @@ | ||
| 443 | 404 | |
| 444 | 405 | <?php |
| 445 | 406 | $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general'); |
| 446 | 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> | |
| 447 | 411 | </div> |
| 448 | 412 | |
| 449 | 413 | <div class="dashboard-card"> |
| 450 | 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> |
| @@ -829,21 +793,21 @@ | ||
| 829 | 793 | } |
| 830 | 794 | }); |
| 831 | 795 | |
| 832 | 796 | function hasPassword() { |
| 833 | - 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]"]'); | |
| 834 | 798 | var passwordValue = passwordField.val(); |
| 835 | 799 | return passwordValue && passwordValue.length > 0; |
| 836 | 800 | } |
| 837 | 801 | |
| 838 | 802 | function hasRecoveryEmail() { |
| 839 | - 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]"]'); | |
| 840 | 804 | var emailValue = recoveryEmailField.val(); |
| 841 | 805 | return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue); |
| 842 | 806 | } |
| 843 | 807 | |
| 844 | - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]'); | |
| 845 | - 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]"]'); | |
| 846 | 810 | |
| 847 | 811 | passwordField.on('blur', function() { |
| 848 | 812 | if (hasPassword() && !hasRecoveryEmail()) { |
| 849 | 813 | showModal( |
| @@ -881,9 +845,9 @@ | ||
| 881 | 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.' |
| 882 | 846 | ); |
| 883 | 847 | |
| 884 | 848 | setTimeout(function() { |
| 885 | - $('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(); | |
| 886 | 850 | }, 300); |
| 887 | 851 | |
| 888 | 852 | return false; |
| 889 | 853 | } |
| @@ -954,10 +918,10 @@ | ||
| 954 | 918 | </div> |
| 955 | 919 | <?php endif; ?> |
| 956 | 920 | |
| 957 | 921 | <?php |
| 958 | - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'): | |
| 959 | - $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 | |
| 960 | 924 | ?> |
| 961 | 925 | <div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 962 | 926 | <p> |
| 963 | 927 | <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>. |
| @@ -964,9 +928,9 @@ | ||
| 964 | 928 | </p> |
| 965 | 929 | </div> |
| 966 | 930 | <?php endif; ?> |
| 967 | 931 | |
| 968 | - <?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 ?> | |
| 969 | 933 | <div class="notice notice-info inline" style="margin-bottom: 20px;"> |
| 970 | 934 | <p> |
| 971 | 935 | <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled. |
| 972 | 936 | </p> |
| @@ -972,9 +936,9 @@ | ||
| 972 | 936 | </p> |
| 973 | 937 | </div> |
| 974 | 938 | <?php endif; ?> |
| 975 | 939 | |
| 976 | - <?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 ?> | |
| 977 | 941 | <div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 978 | 942 | <p> |
| 979 | 943 | <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours. |
| 980 | 944 | </p> |
| @@ -980,9 +944,9 @@ | ||
| 980 | 944 | </p> |
| 981 | 945 | </div> |
| 982 | 946 | <?php endif; ?> |
| 983 | 947 | |
| 984 | - <?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 ?> | |
| 985 | 949 | <div class="notice notice-error inline" style="margin-bottom: 20px;"> |
| 986 | 950 | <p> |
| 987 | 951 | <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again. |
| 988 | 952 | </p> |
| @@ -1019,9 +983,9 @@ | ||
| 1019 | 983 | 'method': 'post', |
| 1020 | 984 | 'action': '' |
| 1021 | 985 | }); |
| 1022 | 986 | |
| 1023 | - 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 ?>'); | |
| 1024 | 988 | |
| 1025 | 989 | logoutForm.append($('<input>', { |
| 1026 | 990 | 'type': 'hidden', |
| 1027 | 991 | 'name': 'whitelabel_logout', |
| @@ -1093,9 +1057,9 @@ | ||
| 1093 | 1057 | $.ajax({ |
| 1094 | 1058 | url: ajaxUrl, |
| 1095 | 1059 | type: 'POST', |
| 1096 | 1060 | dataType: 'json', |
| 1097 | - 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')); ?>' }, | |
| 1098 | 1062 | timeout: 35000, |
| 1099 | 1063 | success: function(response, textStatus, xhr) { |
| 1100 | 1064 | try { |
| 1101 | 1065 | if (response && response.success && response.data) { |
| @@ -1163,22 +1127,22 @@ | ||
| 1163 | 1127 | var blockedClass = result.blocked ? 'blocked' : 'allowed'; |
| 1164 | 1128 | |
| 1165 | 1129 | html += '<div class="test-result-item ' + statusClass + '">'; |
| 1166 | 1130 | html += '<div class="test-result-header">'; |
| 1167 | - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>'; | |
| 1131 | + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>'; | |
| 1168 | 1132 | html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>'; |
| 1169 | 1133 | html += '</div>'; |
| 1170 | 1134 | |
| 1171 | 1135 | 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>'; | |
| 1136 | + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>'; | |
| 1137 | + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>'; | |
| 1174 | 1138 | |
| 1175 | 1139 | if (result.status_code) { |
| 1176 | - 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>'; | |
| 1177 | 1141 | } |
| 1178 | 1142 | |
| 1179 | 1143 | if (result.error) { |
| 1180 | - 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>'; | |
| 1181 | 1145 | } |
| 1182 | 1146 | |
| 1183 | 1147 | if (result.body) { |
| 1184 | 1148 | html += '<p><strong>Response Body:</strong></p>'; |
| @@ -1188,9 +1152,9 @@ | ||
| 1188 | 1152 | if (result.headers && Object.keys(result.headers).length > 0) { |
| 1189 | 1153 | html += '<p><strong>Response Headers:</strong></p>'; |
| 1190 | 1154 | html += '<pre class="response-headers">'; |
| 1191 | 1155 | Object.keys(result.headers).forEach(function(key) { |
| 1192 | - html += key + ': ' + result.headers[key] + '\n'; | |
| 1156 | + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n'; | |
| 1193 | 1157 | }); |
| 1194 | 1158 | html += '</pre>'; |
| 1195 | 1159 | } |
| 1196 | 1160 | |
| @@ -1203,9 +1167,9 @@ | ||
| 1203 | 1167 | html += '<p><strong>Parsed Response:</strong></p>'; |
| 1204 | 1168 | html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>'; |
| 1205 | 1169 | } |
| 1206 | 1170 | |
| 1207 | - html += '<p><strong>Details:</strong> ' + result.details + '</p>'; | |
| 1171 | + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>'; | |
| 1208 | 1172 | html += '</div>'; |
| 1209 | 1173 | html += '</div>'; |
| 1210 | 1174 | }); |
| 1211 | 1175 | |
| @@ -1220,9 +1184,9 @@ | ||
| 1220 | 1184 | '>': '>', |
| 1221 | 1185 | '"': '"', |
| 1222 | 1186 | '\'': ''' |
| 1223 | 1187 | }; |
| 1224 | - return text.replace(/[&<>"']/g, function(m) { return map[m]; }); | |
| 1188 | + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); | |
| 1225 | 1189 | } |
| 1226 | 1190 | }); |
| 1227 | 1191 | </script> |
| 1228 | 1192 | |
| @@ -1355,41 +1319,41 @@ | ||
| 1355 | 1319 | } |
| 1356 | 1320 | </style> |
| 1357 | 1321 | |
| 1358 | 1322 | <?php if ($active_tab === 'advanced'): ?> |
| 1359 | - <?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 ?> | |
| 1360 | 1324 | <div class="notice notice-success is-dismissible"> |
| 1361 | 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> |
| 1362 | 1326 | </div> |
| 1363 | 1327 | <?php endif; ?> |
| 1364 | 1328 | |
| 1365 | - <?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 ?> | |
| 1366 | 1330 | <div class="notice notice-error is-dismissible"> |
| 1367 | 1331 | <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p> |
| 1368 | 1332 | </div> |
| 1369 | 1333 | <?php endif; ?> |
| 1370 | 1334 | |
| 1371 | - <?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 ?> | |
| 1372 | 1336 | <div class="notice notice-success is-dismissible"> |
| 1373 | 1337 | <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p> |
| 1374 | 1338 | </div> |
| 1375 | 1339 | <?php endif; ?> |
| 1376 | 1340 | |
| 1377 | - <?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 ?> | |
| 1378 | 1342 | <div class="notice notice-error is-dismissible"> |
| 1379 | 1343 | <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p> |
| 1380 | 1344 | </div> |
| 1381 | 1345 | <?php endif; ?> |
| 1382 | 1346 | |
| 1383 | - <?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 ?> | |
| 1384 | 1348 | <div class="notice notice-success is-dismissible"> |
| 1385 | 1349 | <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p> |
| 1386 | 1350 | </div> |
| 1387 | 1351 | <?php endif; ?> |
| 1388 | 1352 | |
| 1389 | - <?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 ?> | |
| 1390 | 1354 | <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> | |
| 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> | |
| 1392 | 1356 | </div> |
| 1393 | 1357 | <?php endif; ?> |
| 1394 | 1358 | |
| 1395 | 1359 | <?php endif; ?> |
| @@ -1411,9 +1375,9 @@ | ||
| 1411 | 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> |
| 1412 | 1376 | <?php |
| 1413 | 1377 | if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') { |
| 1414 | 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>'; |
| 1415 | - 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>'; | |
| 1416 | 1380 | } else { |
| 1417 | 1381 | echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>'; |
| 1418 | 1382 | } |
| 1419 | 1383 | ?> |
| @@ -1423,8 +1387,15 @@ | ||
| 1423 | 1387 | } |
| 1424 | 1388 | |
| 1425 | 1389 | public function create_admin_robots_txt_page() |
| 1426 | 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 | + | |
| 1427 | 1398 | ?> |
| 1428 | 1399 | <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?> |
| 1429 | 1400 | |
| 1430 | 1401 | <?php |
| @@ -1445,8 +1416,11 @@ | ||
| 1445 | 1416 | |
| 1446 | 1417 | if (is_wp_error($result)) { |
| 1447 | 1418 | echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>'; |
| 1448 | 1419 | } else { |
| 1420 | + Metasync_PostHog::feature('robots_txt_saved', array( | |
| 1421 | + 'warning_count' => count($validation['warnings']), | |
| 1422 | + )); | |
| 1449 | 1423 | echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>'; |
| 1450 | 1424 | |
| 1451 | 1425 | if (!empty($validation['warnings'])) { |
| 1452 | 1426 | foreach ($validation['warnings'] as $warning) { |
| @@ -1487,84 +1461,8 @@ | ||
| 1487 | 1461 | { |
| 1488 | 1462 | require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php'; |
| 1489 | 1463 | } |
| 1490 | 1464 | |
| 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 | 1465 | public function create_admin_custom_pages_page() |
| 1568 | 1466 | { |
| 1569 | 1467 | ?> |
| 1570 | 1468 | <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?> |
| @@ -1615,12 +1513,14 @@ | ||
| 1615 | 1513 | } |
| 1616 | 1514 | |
| 1617 | 1515 | public function create_admin_breadcrumbs_page() |
| 1618 | 1516 | { |
| 1619 | - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs'; | |
| 1620 | 1517 | ?> |
| 1621 | 1518 | <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?> |
| 1622 | 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 | + | |
| 1623 | 1523 | <form method="post" action="options.php"> |
| 1624 | 1524 | <div class="dashboard-card"> |
| 1625 | 1525 | <h2>Breadcrumbs Settings</h2> |
| 1626 | 1526 | <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p> |
| @@ -1625,9 +1525,9 @@ | ||
| 1625 | 1525 | <h2>Breadcrumbs Settings</h2> |
| 1626 | 1526 | <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p> |
| 1627 | 1527 | <?php |
| 1628 | 1528 | settings_fields(Metasync_Admin::option_group); |
| 1629 | - do_settings_sections($page_slug); | |
| 1529 | + Metasync_Settings_Fields::instance()->render_breadcrumbs_section(); | |
| 1630 | 1530 | submit_button(); |
| 1631 | 1531 | ?> |
| 1632 | 1532 | </div> |
| 1633 | 1533 | </form> |
| @@ -1747,14 +1647,8 @@ | ||
| 1747 | 1647 | </div> |
| 1748 | 1648 | </form> |
| 1749 | 1649 | <?php $this->admin->render_layout_close(); ?> |
| 1750 | 1650 | <?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 | 1651 | } |
| 1758 | 1652 | |
| 1759 | 1653 | public function create_admin_google_console_page() |
| 1760 | 1654 | { |