| @@ -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> |
| @@ -394,8 +404,11 @@ | ||
| 394 | 404 | |
| 395 | 405 | <?php |
| 396 | 406 | $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general'); |
| 397 | 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> | |
| 398 | 411 | </div> |
| 399 | 412 | |
| 400 | 413 | <div class="dashboard-card"> |
| 401 | 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> |
| @@ -780,21 +793,21 @@ | ||
| 780 | 793 | } |
| 781 | 794 | }); |
| 782 | 795 | |
| 783 | 796 | function hasPassword() { |
| 784 | - 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]"]'); | |
| 785 | 798 | var passwordValue = passwordField.val(); |
| 786 | 799 | return passwordValue && passwordValue.length > 0; |
| 787 | 800 | } |
| 788 | 801 | |
| 789 | 802 | function hasRecoveryEmail() { |
| 790 | - 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]"]'); | |
| 791 | 804 | var emailValue = recoveryEmailField.val(); |
| 792 | 805 | return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue); |
| 793 | 806 | } |
| 794 | 807 | |
| 795 | - var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]'); | |
| 796 | - 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]"]'); | |
| 797 | 810 | |
| 798 | 811 | passwordField.on('blur', function() { |
| 799 | 812 | if (hasPassword() && !hasRecoveryEmail()) { |
| 800 | 813 | showModal( |
| @@ -832,9 +845,9 @@ | ||
| 832 | 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.' |
| 833 | 846 | ); |
| 834 | 847 | |
| 835 | 848 | setTimeout(function() { |
| 836 | - $('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(); | |
| 837 | 850 | }, 300); |
| 838 | 851 | |
| 839 | 852 | return false; |
| 840 | 853 | } |
| @@ -905,10 +918,10 @@ | ||
| 905 | 918 | </div> |
| 906 | 919 | <?php endif; ?> |
| 907 | 920 | |
| 908 | 921 | <?php |
| 909 | - if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'): | |
| 910 | - $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 | |
| 911 | 924 | ?> |
| 912 | 925 | <div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 913 | 926 | <p> |
| 914 | 927 | <strong>✅ Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>. |
| @@ -915,9 +928,9 @@ | ||
| 915 | 928 | </p> |
| 916 | 929 | </div> |
| 917 | 930 | <?php endif; ?> |
| 918 | 931 | |
| 919 | - <?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 ?> | |
| 920 | 933 | <div class="notice notice-info inline" style="margin-bottom: 20px;"> |
| 921 | 934 | <p> |
| 922 | 935 | <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled. |
| 923 | 936 | </p> |
| @@ -923,9 +936,9 @@ | ||
| 923 | 936 | </p> |
| 924 | 937 | </div> |
| 925 | 938 | <?php endif; ?> |
| 926 | 939 | |
| 927 | - <?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 ?> | |
| 928 | 941 | <div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 929 | 942 | <p> |
| 930 | 943 | <strong>✅ Extended!</strong> Debug mode has been extended for another 24 hours. |
| 931 | 944 | </p> |
| @@ -931,9 +944,9 @@ | ||
| 931 | 944 | </p> |
| 932 | 945 | </div> |
| 933 | 946 | <?php endif; ?> |
| 934 | 947 | |
| 935 | - <?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 ?> | |
| 936 | 949 | <div class="notice notice-error inline" style="margin-bottom: 20px;"> |
| 937 | 950 | <p> |
| 938 | 951 | <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again. |
| 939 | 952 | </p> |
| @@ -970,9 +983,9 @@ | ||
| 970 | 983 | 'method': 'post', |
| 971 | 984 | 'action': '' |
| 972 | 985 | }); |
| 973 | 986 | |
| 974 | - 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 ?>'); | |
| 975 | 988 | |
| 976 | 989 | logoutForm.append($('<input>', { |
| 977 | 990 | 'type': 'hidden', |
| 978 | 991 | 'name': 'whitelabel_logout', |
| @@ -1044,9 +1057,9 @@ | ||
| 1044 | 1057 | $.ajax({ |
| 1045 | 1058 | url: ajaxUrl, |
| 1046 | 1059 | type: 'POST', |
| 1047 | 1060 | dataType: 'json', |
| 1048 | - 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')); ?>' }, | |
| 1049 | 1062 | timeout: 35000, |
| 1050 | 1063 | success: function(response, textStatus, xhr) { |
| 1051 | 1064 | try { |
| 1052 | 1065 | if (response && response.success && response.data) { |
| @@ -1114,22 +1127,22 @@ | ||
| 1114 | 1127 | var blockedClass = result.blocked ? 'blocked' : 'allowed'; |
| 1115 | 1128 | |
| 1116 | 1129 | html += '<div class="test-result-item ' + statusClass + '">'; |
| 1117 | 1130 | html += '<div class="test-result-header">'; |
| 1118 | - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>'; | |
| 1131 | + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>'; | |
| 1119 | 1132 | html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>'; |
| 1120 | 1133 | html += '</div>'; |
| 1121 | 1134 | |
| 1122 | 1135 | 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>'; | |
| 1136 | + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>'; | |
| 1137 | + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>'; | |
| 1125 | 1138 | |
| 1126 | 1139 | if (result.status_code) { |
| 1127 | - 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>'; | |
| 1128 | 1141 | } |
| 1129 | 1142 | |
| 1130 | 1143 | if (result.error) { |
| 1131 | - 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>'; | |
| 1132 | 1145 | } |
| 1133 | 1146 | |
| 1134 | 1147 | if (result.body) { |
| 1135 | 1148 | html += '<p><strong>Response Body:</strong></p>'; |
| @@ -1139,9 +1152,9 @@ | ||
| 1139 | 1152 | if (result.headers && Object.keys(result.headers).length > 0) { |
| 1140 | 1153 | html += '<p><strong>Response Headers:</strong></p>'; |
| 1141 | 1154 | html += '<pre class="response-headers">'; |
| 1142 | 1155 | Object.keys(result.headers).forEach(function(key) { |
| 1143 | - html += key + ': ' + result.headers[key] + '\n'; | |
| 1156 | + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n'; | |
| 1144 | 1157 | }); |
| 1145 | 1158 | html += '</pre>'; |
| 1146 | 1159 | } |
| 1147 | 1160 | |
| @@ -1154,9 +1167,9 @@ | ||
| 1154 | 1167 | html += '<p><strong>Parsed Response:</strong></p>'; |
| 1155 | 1168 | html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>'; |
| 1156 | 1169 | } |
| 1157 | 1170 | |
| 1158 | - html += '<p><strong>Details:</strong> ' + result.details + '</p>'; | |
| 1171 | + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>'; | |
| 1159 | 1172 | html += '</div>'; |
| 1160 | 1173 | html += '</div>'; |
| 1161 | 1174 | }); |
| 1162 | 1175 | |
| @@ -1171,9 +1184,9 @@ | ||
| 1171 | 1184 | '>': '>', |
| 1172 | 1185 | '"': '"', |
| 1173 | 1186 | '\'': ''' |
| 1174 | 1187 | }; |
| 1175 | - return text.replace(/[&<>"']/g, function(m) { return map[m]; }); | |
| 1188 | + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); | |
| 1176 | 1189 | } |
| 1177 | 1190 | }); |
| 1178 | 1191 | </script> |
| 1179 | 1192 | |
| @@ -1306,41 +1319,41 @@ | ||
| 1306 | 1319 | } |
| 1307 | 1320 | </style> |
| 1308 | 1321 | |
| 1309 | 1322 | <?php if ($active_tab === 'advanced'): ?> |
| 1310 | - <?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 ?> | |
| 1311 | 1324 | <div class="notice notice-success is-dismissible"> |
| 1312 | 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> |
| 1313 | 1326 | </div> |
| 1314 | 1327 | <?php endif; ?> |
| 1315 | 1328 | |
| 1316 | - <?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 ?> | |
| 1317 | 1330 | <div class="notice notice-error is-dismissible"> |
| 1318 | 1331 | <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p> |
| 1319 | 1332 | </div> |
| 1320 | 1333 | <?php endif; ?> |
| 1321 | 1334 | |
| 1322 | - <?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 ?> | |
| 1323 | 1336 | <div class="notice notice-success is-dismissible"> |
| 1324 | 1337 | <p><strong>✅ Success!</strong> Error logs have been cleared successfully.</p> |
| 1325 | 1338 | </div> |
| 1326 | 1339 | <?php endif; ?> |
| 1327 | 1340 | |
| 1328 | - <?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 ?> | |
| 1329 | 1342 | <div class="notice notice-error is-dismissible"> |
| 1330 | 1343 | <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p> |
| 1331 | 1344 | </div> |
| 1332 | 1345 | <?php endif; ?> |
| 1333 | 1346 | |
| 1334 | - <?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 ?> | |
| 1335 | 1348 | <div class="notice notice-success is-dismissible"> |
| 1336 | 1349 | <p><strong>✅ Success!</strong> Plugin access roles have been saved successfully.</p> |
| 1337 | 1350 | </div> |
| 1338 | 1351 | <?php endif; ?> |
| 1339 | 1352 | |
| 1340 | - <?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 ?> | |
| 1341 | 1354 | <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> | |
| 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> | |
| 1343 | 1356 | </div> |
| 1344 | 1357 | <?php endif; ?> |
| 1345 | 1358 | |
| 1346 | 1359 | <?php endif; ?> |
| @@ -1362,9 +1375,9 @@ | ||
| 1362 | 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> |
| 1363 | 1376 | <?php |
| 1364 | 1377 | if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') { |
| 1365 | 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>'; |
| 1366 | - 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>'; | |
| 1367 | 1380 | } else { |
| 1368 | 1381 | echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>'; |
| 1369 | 1382 | } |
| 1370 | 1383 | ?> |
| @@ -1374,8 +1387,15 @@ | ||
| 1374 | 1387 | } |
| 1375 | 1388 | |
| 1376 | 1389 | public function create_admin_robots_txt_page() |
| 1377 | 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 | + | |
| 1378 | 1398 | ?> |
| 1379 | 1399 | <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?> |
| 1380 | 1400 | |
| 1381 | 1401 | <?php |
| @@ -1396,8 +1416,11 @@ | ||
| 1396 | 1416 | |
| 1397 | 1417 | if (is_wp_error($result)) { |
| 1398 | 1418 | echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>'; |
| 1399 | 1419 | } else { |
| 1420 | + Metasync_PostHog::feature('robots_txt_saved', array( | |
| 1421 | + 'warning_count' => count($validation['warnings']), | |
| 1422 | + )); | |
| 1400 | 1423 | echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>'; |
| 1401 | 1424 | |
| 1402 | 1425 | if (!empty($validation['warnings'])) { |
| 1403 | 1426 | foreach ($validation['warnings'] as $warning) { |
| @@ -1438,84 +1461,8 @@ | ||
| 1438 | 1461 | { |
| 1439 | 1462 | require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php'; |
| 1440 | 1463 | } |
| 1441 | 1464 | |
| 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 | 1465 | public function create_admin_custom_pages_page() |
| 1519 | 1466 | { |
| 1520 | 1467 | ?> |
| 1521 | 1468 | <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?> |
| @@ -1566,12 +1513,14 @@ | ||
| 1566 | 1513 | } |
| 1567 | 1514 | |
| 1568 | 1515 | public function create_admin_breadcrumbs_page() |
| 1569 | 1516 | { |
| 1570 | - $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs'; | |
| 1571 | 1517 | ?> |
| 1572 | 1518 | <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?> |
| 1573 | 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 | + | |
| 1574 | 1523 | <form method="post" action="options.php"> |
| 1575 | 1524 | <div class="dashboard-card"> |
| 1576 | 1525 | <h2>Breadcrumbs Settings</h2> |
| 1577 | 1526 | <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p> |
| @@ -1576,9 +1525,9 @@ | ||
| 1576 | 1525 | <h2>Breadcrumbs Settings</h2> |
| 1577 | 1526 | <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p> |
| 1578 | 1527 | <?php |
| 1579 | 1528 | settings_fields(Metasync_Admin::option_group); |
| 1580 | - do_settings_sections($page_slug); | |
| 1529 | + Metasync_Settings_Fields::instance()->render_breadcrumbs_section(); | |
| 1581 | 1530 | submit_button(); |
| 1582 | 1531 | ?> |
| 1583 | 1532 | </div> |
| 1584 | 1533 | </form> |
| @@ -1698,14 +1647,8 @@ | ||
| 1698 | 1647 | </div> |
| 1699 | 1648 | </form> |
| 1700 | 1649 | <?php $this->admin->render_layout_close(); ?> |
| 1701 | 1650 | <?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 | 1651 | } |
| 1709 | 1652 | |
| 1710 | 1653 | public function create_admin_google_console_page() |
| 1711 | 1654 | { |