| @@ -57,9 +57,9 @@ | ||
| 57 | 57 | $post_types = array_diff($post_types, ['attachment']); |
| 58 | 58 | |
| 59 | 59 | // Resolve the current post ID in admin_init context: $_GET['post'] on the |
| 60 | 60 | // edit screen, $_POST['post_ID'] on save. Mirrors the sidebar enqueue. |
| 61 | - $post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0); | |
| 61 | + $post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- read-only post ID to pick meta boxes | |
| 62 | 62 | |
| 63 | 63 | // LPS / custom-HTML pages carry their own baked SEO served before wp_head, |
| 64 | 64 | // so the editable SEO fields here do nothing and mislead editors. |
| 65 | 65 | // otto_pixel.php (where metasync_is_custom_or_lps_page lives) is only |
| @@ -297,9 +297,9 @@ | ||
| 297 | 297 | if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) { |
| 298 | 298 | return; |
| 299 | 299 | } |
| 300 | 300 | |
| 301 | - $post_data = metasync_sanitize_input_array($_POST); | |
| 301 | + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write | |
| 302 | 302 | // Check for new field name first, then old for backward compatibility |
| 303 | 303 | $field_name = isset($post_data['common_robots_meta']) ? 'common_robots_meta' : 'common_robots_mata'; |
| 304 | 304 | |
| 305 | 305 | // Unchecked checkboxes are omitted from the request. Require the nonce, |
| @@ -331,9 +331,9 @@ | ||
| 331 | 331 | if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) { |
| 332 | 332 | return; |
| 333 | 333 | } |
| 334 | 334 | |
| 335 | - $post_data = metasync_sanitize_input_array($_POST); | |
| 335 | + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write | |
| 336 | 336 | // Check for new field name first, then old for backward compatibility |
| 337 | 337 | $field_name = isset($post_data['advanced_robots_meta']) ? 'advanced_robots_meta' : 'advanced_robots_mata'; |
| 338 | 338 | |
| 339 | 339 | if (!isset($post_data['metasync_advance_robots_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_advance_robots_nonce'], 'metasync_advance_robots_nonce')) |
| @@ -353,9 +353,9 @@ | ||
| 353 | 353 | { |
| 354 | 354 | if (!current_user_can('edit_post', $post_id)) |
| 355 | 355 | return; |
| 356 | 356 | |
| 357 | - $post_data = metasync_sanitize_input_array($_POST); | |
| 357 | + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write | |
| 358 | 358 | // Check for new field name first, then old for backward compatibility |
| 359 | 359 | $field_name = isset($post_data['post_redirect_meta']) ? 'post_redirect_meta' : 'post_redirect_mata'; |
| 360 | 360 | |
| 361 | 361 | if (!isset($post_data['metasync_post_redirection_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_post_redirection_nonce'], 'metasync_post_redirection_nonce')) |
| @@ -462,8 +462,21 @@ | ||
| 462 | 462 | // keep the previously stored rule and skip the write. The guard above |
| 463 | 463 | // already returned when a non-410/451 type had an empty destination, |
| 464 | 464 | // so $dest is known non-empty here. |
| 465 | 465 | if (!$is_gone) { |
| 466 | + // Destination guard — same checks as the admin form and MCP tools. | |
| 467 | + // Backslashes and protocol-relative hosts bypass host validation in | |
| 468 | + // browsers while looking internal to wp_validate_redirect. On a | |
| 469 | + // bad destination we keep the previously stored rule and skip the | |
| 470 | + // write, same skip-write semantics as the loop guard below. | |
| 471 | + if (!class_exists('Metasync_Redirection_Validator')) { | |
| 472 | + require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-validator.php'; | |
| 473 | + } | |
| 474 | + if (!Metasync_Redirection_Validator::is_safe_destination_syntax($dest) | |
| 475 | + || (!get_option('metasync_allow_external_redirects', 0) && wp_validate_redirect($dest, '') !== $dest)) { | |
| 476 | + return; | |
| 477 | + } | |
| 478 | + | |
| 466 | 479 | if (!class_exists('Metasync_Redirection')) { |
| 467 | 480 | require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection.php'; |
| 468 | 481 | } |
| 469 | 482 | $db_ref = $db; |
| @@ -498,9 +511,9 @@ | ||
| 498 | 511 | { |
| 499 | 512 | if (!current_user_can('edit_post', $post_id)) |
| 500 | 513 | return; |
| 501 | 514 | |
| 502 | - $post_data = metasync_sanitize_input_array($_POST); | |
| 515 | + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write | |
| 503 | 516 | // Check for new field name first, then old for backward compatibility |
| 504 | 517 | $field_name = isset($post_data['post_canonical_url_meta']) ? 'post_canonical_url_meta' : 'post_canonical_url_mata'; |
| 505 | 518 | |
| 506 | 519 | if (!isset($post_data['metasync_post_canonical_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_post_canonical_nonce'], 'metasync_post_canonical_nonce')) |
| @@ -661,12 +674,14 @@ | ||
| 661 | 674 | |
| 662 | 675 | wp_nonce_field('metasync_seo_meta_nonce', 'metasync_seo_meta_nonce'); |
| 663 | 676 | ?> |
| 664 | 677 | <p style="color: #666; margin-bottom: 12px;"> |
| 678 | + <?php /* translators: %s: OTTO name (whitelabel). */ ?> | |
| 665 | 679 | <?php printf(esc_html__('Set the SEO Title and Meta Description for this post. Leave the field blank to use %s suggestion.', 'metasync'), esc_html(Metasync::get_whitelabel_otto_name())); ?> |
| 666 | 680 | </p> |
| 667 | 681 | <?php if ($title_is_imported) : ?> |
| 668 | 682 | <p style="color: #666; margin-bottom: 12px;"> |
| 683 | + <?php /* translators: %s: OTTO name (whitelabel). */ ?> | |
| 669 | 684 | <?php printf(esc_html__('The greyed-out value below was imported from another SEO plugin. It is used only until %s has a suggestion for this page.', 'metasync'), esc_html(Metasync::get_whitelabel_otto_name())); ?> |
| 670 | 685 | </p> |
| 671 | 686 | <?php endif; ?> |
| 672 | 687 | <table class="form-table" style="margin: 0;"> |
| @@ -706,11 +721,13 @@ | ||
| 706 | 721 | return; |
| 707 | 722 | } |
| 708 | 723 | |
| 709 | 724 | if (isset($_POST['metasync_seo_title'])) { |
| 710 | - $seo_title = sanitize_text_field($_POST['metasync_seo_title']); | |
| 725 | + $seo_title = sanitize_text_field(wp_unslash($_POST['metasync_seo_title'])); | |
| 711 | 726 | if (!empty($seo_title)) { |
| 712 | - update_post_meta($post_id, '_metasync_seo_title', $seo_title); | |
| 727 | + // update_post_meta() unslashes again, so re-slash to keep | |
| 728 | + // literal backslashes and quotes in the stored value. | |
| 729 | + update_post_meta($post_id, '_metasync_seo_title', wp_slash($seo_title)); | |
| 713 | 730 | } else { |
| 714 | 731 | delete_post_meta($post_id, '_metasync_seo_title'); |
| 715 | 732 | } |
| 716 | 733 | } |
| @@ -715,11 +732,11 @@ | ||
| 715 | 732 | } |
| 716 | 733 | } |
| 717 | 734 | |
| 718 | 735 | if (isset($_POST['metasync_seo_desc'])) { |
| 719 | - $seo_desc = sanitize_textarea_field($_POST['metasync_seo_desc']); | |
| 736 | + $seo_desc = sanitize_textarea_field(wp_unslash($_POST['metasync_seo_desc'])); | |
| 720 | 737 | if (!empty($seo_desc)) { |
| 721 | - update_post_meta($post_id, '_metasync_seo_desc', $seo_desc); | |
| 738 | + update_post_meta($post_id, '_metasync_seo_desc', wp_slash($seo_desc)); | |
| 722 | 739 | } else { |
| 723 | 740 | delete_post_meta($post_id, '_metasync_seo_desc'); |
| 724 | 741 | } |
| 725 | 742 | } |