| @@ -90,9 +90,9 @@ | ||
| 90 | 90 | * |
| 91 | 91 | * @since 3.16.0 |
| 92 | 92 | * @var string The URL of the suggested link. |
| 93 | 93 | */ |
| 94 | - protected $href; | |
| 94 | + protected $href = ''; | |
| 95 | 95 | |
| 96 | 96 | /** |
| 97 | 97 | * The title of the suggested link. |
| 98 | 98 | * |
| @@ -729,21 +729,48 @@ | ||
| 729 | 729 | $this->uid = $uid; |
| 730 | 730 | } |
| 731 | 731 | |
| 732 | 732 | /** |
| 733 | + * Determines whether an href is valid for a Smart Link. | |
| 734 | + * | |
| 735 | + * Only web URLs are accepted. Scheme-less references such as `/path/` or | |
| 736 | + * `#anchor` also pass, unless they contain a colon, which WordPress reads | |
| 737 | + * as a scheme delimiter. `esc_url_raw()` is used instead of a scheme | |
| 738 | + * comparison, as it also catches case and whitespace variations. | |
| 739 | + * | |
| 740 | + * @since 3.24.2 | |
| 741 | + * | |
| 742 | + * @param string $href The href to check. | |
| 743 | + * @return bool Whether the href is valid. | |
| 744 | + */ | |
| 745 | + public static function is_valid_href( string $href ): bool { | |
| 746 | + return '' !== esc_url_raw( $href, array( 'http', 'https' ) ); | |
| 747 | + } | |
| 748 | + | |
| 749 | + /** | |
| 733 | 750 | * Sets the href of the smart link. |
| 734 | 751 | * |
| 752 | + * Invalid hrefs are ignored. Callers accepting user input should validate | |
| 753 | + * with `is_valid_href()` first, to report the rejection. | |
| 754 | + * | |
| 755 | + * No canonical URL is stored, as this runs during REST argument | |
| 756 | + * validation, which must not have side effects. | |
| 757 | + * | |
| 735 | 758 | * @since 3.16.0 |
| 759 | + * @since 3.24.2 Invalid hrefs are ignored, and no canonical URL is stored. | |
| 736 | 760 | * |
| 737 | 761 | * @param string $href The href of the smart link. |
| 738 | 762 | */ |
| 739 | 763 | public function set_href( string $href ): void { |
| 764 | + if ( ! self::is_valid_href( $href ) ) { | |
| 765 | + return; | |
| 766 | + } | |
| 767 | + | |
| 740 | 768 | $this->href = $href; |
| 741 | 769 | $destination_post_id = Utils::get_post_id_by_url( $href ); |
| 742 | 770 | |
| 743 | 771 | if ( 0 !== $destination_post_id ) { |
| 744 | - // Set the destination post ID, and update the canonical URL. | |
| 745 | - $this->set_destination_post_id( $destination_post_id, $href ); | |
| 772 | + $this->set_destination_post_id( $destination_post_id ); | |
| 746 | 773 | } |
| 747 | 774 | } |
| 748 | 775 | |
| 749 | 776 | /** |
| @@ -949,9 +976,9 @@ | ||
| 949 | 976 | } |
| 950 | 977 | // Build the query arguments. |
| 951 | 978 | $query_args = array( |
| 952 | 979 | 'post_type' => 'parsely_smart_link', |
| 953 | - 'posts_per_page' => -1, | |
| 980 | + 'posts_per_page' => -1, // phpcs:ignore WordPressVIPMinimum.Performance.NoPaging.posts_per_page_posts_per_page | |
| 954 | 981 | 'fields' => 'ids', // Only get the post IDs to improve performance. |
| 955 | 982 | // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query |
| 956 | 983 | 'tax_query' => array_merge( array( 'relation' => 'AND' ), $tax_query ), |
| 957 | 984 | ); |