| @@ -1,6 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean | |
| 3 | +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate. | |
| 2 | 4 | |
| 5 | + | |
| 3 | 6 | if ( ! defined( 'ABSPATH' ) ) { |
| 4 | 7 | exit; // Exit if accessed directly |
| 5 | 8 | } |
| 6 | 9 | |
| @@ -36,19 +39,41 @@ | ||
| 36 | 39 | return $this->get_countries(); |
| 37 | 40 | } |
| 38 | 41 | } |
| 39 | 42 | |
| 43 | + /** | |
| 44 | + * Resolve a cookie choice using trusted currency definitions and current rates. | |
| 45 | + */ | |
| 46 | + public function get_currency_from_cookie() { | |
| 47 | + if ( ! isset( $_COOKIE['propertyhive_currency'] ) || ! is_string( $_COOKIE['propertyhive_currency'] ) ) { | |
| 48 | + return false; | |
| 49 | + } | |
| 50 | + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Decode the JSON envelope, then validate its only accepted field against the server's currency definitions below. | |
| 51 | + $stored = json_decode( html_entity_decode( wp_unslash( $_COOKIE['propertyhive_currency'] ) ), true ); | |
| 52 | + if ( ! is_array( $stored ) || ! isset( $stored['currency_code'] ) || ! is_string( $stored['currency_code'] ) ) { | |
| 53 | + return false; | |
| 54 | + } | |
| 55 | + $code = sanitize_text_field( $stored['currency_code'] ); | |
| 56 | + $currency = $this->get_currency( $code ); | |
| 57 | + if ( false === $currency ) { | |
| 58 | + return false; | |
| 59 | + } | |
| 60 | + $rates = get_option( 'propertyhive_currency_exchange_rates', array() ); | |
| 61 | + $currency['exchange_rate'] = isset( $rates[ $code ] ) && is_numeric( $rates[ $code ] ) ? (float) $rates[ $code ] : 1; | |
| 62 | + return $currency; | |
| 63 | + } | |
| 64 | + | |
| 40 | 65 | public function ensure_currency_value_set( $form_controls ) |
| 41 | 66 | { |
| 42 | 67 | if ( isset($form_controls['currency']) ) |
| 43 | 68 | { |
| 44 | - if ( isset($_GET['currency']) && $_GET['currency'] != '' ) | |
| 69 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Currency is a read-only display preference. | |
| 70 | + if ( isset($_GET['currency']) && is_string( $_GET['currency'] ) && $_GET['currency'] != '' ) | |
| 45 | 71 | { |
| 46 | 72 | |
| 47 | 73 | } |
| 48 | - elseif ( isset($_COOKIE['propertyhive_currency']) && $_COOKIE['propertyhive_currency'] != '' ) | |
| 74 | + elseif ( false !== ( $currency = $this->get_currency_from_cookie() ) ) | |
| 49 | 75 | { |
| 50 | - $currency = @json_decode(html_entity_decode($_COOKIE['propertyhive_currency']), TRUE); | |
| 51 | 76 | if ( !empty($currency) && isset($currency['currency_code']) && array_key_exists(ph_clean($currency['currency_code']), $form_controls['currency']['options']) ) |
| 52 | 77 | { |
| 53 | 78 | $form_controls['currency']['value'] = $currency['currency_code']; |
| 54 | 79 | } |
| @@ -59,11 +84,14 @@ | ||
| 59 | 84 | } |
| 60 | 85 | |
| 61 | 86 | public function ph_check_currency_change() |
| 62 | 87 | { |
| 63 | - if ( is_post_type_archive('property') && isset($_GET['currency']) ) | |
| 88 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public currency choice changes only the visitor's display-preference cookie. | |
| 89 | + if ( is_post_type_archive('property') && isset($_GET['currency']) && is_string( $_GET['currency'] ) ) | |
| 64 | 90 | { |
| 65 | - if ( $_GET['currency'] == '' ) | |
| 91 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public currency choice changes only the visitor's display-preference cookie. | |
| 92 | + $currency_code = sanitize_text_field( wp_unslash( $_GET['currency'] ) ); | |
| 93 | + if ( $currency_code == '' ) | |
| 66 | 94 | { |
| 67 | 95 | // Set to blank to reset back to properties entered currency |
| 68 | 96 | unset( $_COOKIE['propertyhive_currency'] ); |
| 69 | 97 | setcookie( 'propertyhive_currency', '', time() - ( 15 * 60 ) ); |
| @@ -69,9 +97,9 @@ | ||
| 69 | 97 | setcookie( 'propertyhive_currency', '', time() - ( 15 * 60 ) ); |
| 70 | 98 | return true; |
| 71 | 99 | } |
| 72 | 100 | |
| 73 | - $currency = $this->get_currency( sanitize_text_field($_GET['currency']) ); | |
| 101 | + $currency = $this->get_currency( $currency_code ); | |
| 74 | 102 | if ( $currency === FALSE ) |
| 75 | 103 | { |
| 76 | 104 | $default_country = get_option( 'propertyhive_default_country', 'GB' ); |
| 77 | 105 | $default_country = $this->get_country( $default_country ); |
| @@ -80,11 +108,11 @@ | ||
| 80 | 108 | } |
| 81 | 109 | |
| 82 | 110 | $currency['exchange_rate'] = 1; |
| 83 | 111 | $exchange_rates = get_option( 'propertyhive_currency_exchange_rates', array() ); |
| 84 | - if ( isset($exchange_rates[$_GET['currency']]) ) | |
| 112 | + if ( isset($exchange_rates[$currency_code]) ) | |
| 85 | 113 | { |
| 86 | - $currency['exchange_rate'] = $exchange_rates[sanitize_text_field($_GET['currency'])]; | |
| 114 | + $currency['exchange_rate'] = $exchange_rates[$currency_code]; | |
| 87 | 115 | } |
| 88 | 116 | |
| 89 | 117 | ph_setcookie( 'propertyhive_currency', htmlentities(json_encode($currency)), time() + (30 * DAY_IN_SECONDS), is_ssl() ); |
| 90 | 118 | } |
| @@ -484,9 +512,9 @@ | ||
| 484 | 512 | echo '<option'; |
| 485 | 513 | if ( $selected_country == $key || ( $selected_country == '' && $key == 'GB' ) ) { |
| 486 | 514 | echo ' selected="selected"'; |
| 487 | 515 | } |
| 488 | - echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_js( $value['name'] ) : $value['name'] ) . '</option>'; | |
| 516 | + echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_js( $value['name'] ) : esc_html( $value['name'] ) ) . '</option>'; | |
| 489 | 517 | } |
| 490 | 518 | } |
| 491 | 519 | } |
| 492 | 520 | |
| @@ -699,9 +727,9 @@ | ||
| 699 | 727 | if ( empty($exchange_rates) ) |
| 700 | 728 | { |
| 701 | 729 | // Get all currency exchange rates from GBP |
| 702 | 730 | // We're using the API from https://github.com/fawazahmed0/exchange-api |
| 703 | - $url = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/gbp.json'; | |
| 731 | + $url = 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/gbp.json'; // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- Retrieves current exchange-rate data from the configured currency service. | |
| 704 | 732 | $response = wp_remote_get( $url ); |
| 705 | 733 | |
| 706 | 734 | if ( is_array( $response ) ) |
| 707 | 735 | { |
| @@ -735,9 +763,9 @@ | ||
| 735 | 763 | if ( !empty( $exchange_rates ) || empty( $previous_exchange_rates ) ) |
| 736 | 764 | { |
| 737 | 765 | $exchange_rates['GBP'] = "1.0000"; |
| 738 | 766 | update_option( 'propertyhive_currency_exchange_rates', $exchange_rates ); |
| 739 | - update_option( 'propertyhive_currency_exchange_rates_updated', date("Y-m-d") ); | |
| 767 | + update_option( 'propertyhive_currency_exchange_rates_updated', gmdate("Y-m-d") ); | |
| 740 | 768 | } |
| 741 | 769 | |
| 742 | 770 | do_action('propertyhive_exchange_rates_updated', $exchange_rates); |
| 743 | 771 | |
| @@ -745,8 +773,9 @@ | ||
| 745 | 773 | $args = array( |
| 746 | 774 | 'post_type' => 'property', |
| 747 | 775 | 'fields' => 'ids', |
| 748 | 776 | 'post_status' => 'publish', |
| 777 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Currency recalculation must select all published on-market properties outside GB using their stored market/country metadata. | |
| 749 | 778 | 'meta_query' => array( |
| 750 | 779 | array( |
| 751 | 780 | 'key' => '_on_market', |
| 752 | 781 | 'value' => 'yes', |