| @@ -1,5 +1,8 @@ | ||
| 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. | |
| 4 | + | |
| 2 | 5 | /** |
| 3 | 6 | * PropertyHive Template |
| 4 | 7 | * |
| 5 | 8 | * Functions for the templating system. |
| @@ -17,8 +20,9 @@ | ||
| 17 | 20 | * |
| 18 | 21 | * @param mixed $post |
| 19 | 22 | * @return PH_Property |
| 20 | 23 | */ |
| 24 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_setup_property_data; the established callable name is part of the plugin/extension API and must remain stable. | |
| 21 | 25 | function ph_setup_property_data( $post ) { |
| 22 | 26 | unset( $GLOBALS['property'] ); |
| 23 | 27 | |
| 24 | 28 | if ( is_int( $post ) ) |
| @@ -26,8 +30,9 @@ | ||
| 26 | 30 | |
| 27 | 31 | if ( empty( $post->post_type ) || ! in_array( $post->post_type, array( 'property' ) ) ) |
| 28 | 32 | return; |
| 29 | 33 | |
| 34 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared frontend template global; Property Hive intentionally publishes the current property object for templates and builder integrations. | |
| 30 | 35 | $GLOBALS['property'] = get_property( $post ); |
| 31 | 36 | |
| 32 | 37 | return $GLOBALS['property']; |
| 33 | 38 | } |
| @@ -38,8 +43,9 @@ | ||
| 38 | 43 | * |
| 39 | 44 | * @access public |
| 40 | 45 | * @return void |
| 41 | 46 | */ |
| 47 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_properties_rss_feed; the established callable name is part of the plugin/extension API and must remain stable. | |
| 42 | 48 | function ph_properties_rss_feed() { |
| 43 | 49 | // Property RSS |
| 44 | 50 | if ( is_post_type_archive( 'property' ) || is_singular( 'property' ) ) { |
| 45 | 51 | |
| @@ -55,8 +61,9 @@ | ||
| 55 | 61 | * |
| 56 | 62 | * @access public |
| 57 | 63 | * @return void |
| 58 | 64 | */ |
| 65 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_generator_tag; the established callable name is part of the plugin/extension API and must remain stable. | |
| 59 | 66 | function ph_generator_tag( $gen, $type ) { |
| 60 | 67 | switch ( $type ) { |
| 61 | 68 | case 'html': |
| 62 | 69 | $gen .= "\n" . '<meta name="generator" content="PropertyHive ' . esc_attr( PH_VERSION ) . '">'; |
| @@ -73,8 +80,9 @@ | ||
| 73 | 80 | * |
| 74 | 81 | * @param array $classes |
| 75 | 82 | * @return array |
| 76 | 83 | */ |
| 84 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_body_class; the established callable name is part of the plugin/extension API and must remain stable. | |
| 77 | 85 | function ph_body_class( $classes ) { |
| 78 | 86 | global $wp_query; |
| 79 | 87 | |
| 80 | 88 | $classes = (array) $classes; |
| @@ -135,8 +143,9 @@ | ||
| 135 | 143 | * @param string|array $class |
| 136 | 144 | * @param int $post_id |
| 137 | 145 | * @return array |
| 138 | 146 | */ |
| 147 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_property_post_class; the established callable name is part of the plugin/extension API and must remain stable. | |
| 139 | 148 | function ph_property_post_class( $classes, $class = '', $post_id = '' ) { |
| 140 | 149 | if ( ! $post_id || get_post_type( $post_id ) !== 'property' ) |
| 141 | 150 | return $classes; |
| 142 | 151 | |
| @@ -205,28 +214,39 @@ | ||
| 205 | 214 | * @return string |
| 206 | 215 | */ |
| 207 | 216 | function propertyhive_page_title( $echo = true ) { |
| 208 | 217 | |
| 209 | - if ( is_search() ) { | |
| 210 | - $page_title = sprintf( __( 'Search Results: “%s”', 'propertyhive' ), get_search_query() ); | |
| 218 | + if ( is_search() ) | |
| 219 | + { | |
| 220 | + $page_title = sprintf( | |
| 221 | + /* translators: %s: search query */ | |
| 222 | + __( 'Search Results: “%s”', 'propertyhive' ), | |
| 223 | + get_search_query() | |
| 224 | + ); | |
| 211 | 225 | |
| 212 | 226 | if ( get_query_var( 'paged' ) ) |
| 213 | - $page_title .= sprintf( __( ' – Page %s', 'propertyhive' ), get_query_var( 'paged' ) ); | |
| 227 | + { | |
| 228 | + $page_title .= sprintf( | |
| 229 | + /* translators: %s: page number */ | |
| 230 | + __( ' – Page %s', 'propertyhive' ), | |
| 231 | + get_query_var( 'paged' ) | |
| 232 | + ); | |
| 233 | + } | |
| 214 | 234 | |
| 215 | - } elseif ( is_tax() ) { | |
| 216 | - | |
| 235 | + }elseif ( is_tax() ) | |
| 236 | + { | |
| 217 | 237 | $page_title = single_term_title( "", false ); |
| 218 | - | |
| 219 | - } else { | |
| 220 | - | |
| 238 | + } | |
| 239 | + else | |
| 240 | + { | |
| 221 | 241 | $search_results_page_id = ph_get_page_id( 'search_results' ); |
| 222 | 242 | $page_title = get_the_title( $search_results_page_id ); |
| 223 | - | |
| 224 | 243 | } |
| 225 | 244 | |
| 226 | - $page_title = apply_filters( 'propertyhive_page_title', $page_title ); | |
| 245 | + $page_title = apply_filters( 'propertyhive_page_title', wp_kses_post( $page_title ) ); | |
| 227 | 246 | |
| 228 | 247 | if ( $echo ) |
| 248 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The core title is KSES-filtered before the trusted PHP propertyhive_page_title filter, which intentionally supports formatted titles. | |
| 229 | 249 | echo $page_title; |
| 230 | 250 | else |
| 231 | 251 | return $page_title; |
| 232 | 252 | } |
| @@ -244,8 +264,9 @@ | ||
| 244 | 264 | function propertyhive_property_loop_start( $echo = true ) { |
| 245 | 265 | ob_start(); |
| 246 | 266 | ph_get_template( 'search/loop-start.php' ); |
| 247 | 267 | if ( $echo ) |
| 268 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Captured trusted PHP loop template; the built-in template emits static list markup and theme overrides own their escaping. | |
| 248 | 269 | echo ob_get_clean(); |
| 249 | 270 | else |
| 250 | 271 | return ob_get_clean(); |
| 251 | 272 | } |
| @@ -264,8 +285,9 @@ | ||
| 264 | 285 | |
| 265 | 286 | ph_get_template( 'search/loop-end.php' ); |
| 266 | 287 | |
| 267 | 288 | if ( $echo ) |
| 289 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Captured trusted PHP loop template; the built-in template emits static list markup and theme overrides own their escaping. | |
| 268 | 290 | echo ob_get_clean(); |
| 269 | 291 | else |
| 270 | 292 | return ob_get_clean(); |
| 271 | 293 | } |
| @@ -280,8 +302,9 @@ | ||
| 280 | 302 | * @subpackage Loop |
| 281 | 303 | * @return void |
| 282 | 304 | */ |
| 283 | 305 | function propertyhive_template_loop_property_thumbnail() { |
| 306 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound, WordPress.Security.EscapeOutput.OutputNotEscaped -- The thumbnail helper escapes image attributes; preserve its trusted PHP override and placeholder HTML hook, and the existing image-size hook name. | |
| 284 | 307 | echo propertyhive_get_property_thumbnail( apply_filters( 'property_search_results_thumbnail_size', 'medium' ) ); |
| 285 | 308 | } |
| 286 | 309 | } |
| 287 | 310 | |
| @@ -302,9 +325,9 @@ | ||
| 302 | 325 | |
| 303 | 326 | $photo_url = $property->get_main_photo_src( $size ); |
| 304 | 327 | |
| 305 | 328 | if ($photo_url !== FALSE) |
| 306 | - return '<img src="' . $photo_url . '" alt="' . get_the_title($post->ID) . '" class="' . $class . '">'; | |
| 329 | + return '<img src="' . esc_url( $photo_url ) . '" alt="' . esc_attr( get_the_title($post->ID) ) . '" class="' . esc_attr( $class ) . '">'; | |
| 307 | 330 | |
| 308 | 331 | if ( ph_placeholder_img_src() ) |
| 309 | 332 | return ph_placeholder_img( $size ); |
| 310 | 333 | } |
| @@ -464,13 +487,15 @@ | ||
| 464 | 487 | function propertyhive_catalog_ordering( $department = '', $orderby = '' ) { |
| 465 | 488 | |
| 466 | 489 | if ( $orderby === '' ) |
| 467 | 490 | { |
| 468 | - $orderby = isset( $_GET['orderby'] ) ? ph_clean( sanitize_text_field($_GET['orderby']) ) : apply_filters( 'propertyhive_default_search_results_orderby', get_option( 'propertyhive_default_search_results_orderby' ) ); | |
| 491 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public result ordering; no state change. | |
| 492 | + $orderby = isset( $_GET['orderby'] ) && is_string( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : apply_filters( 'propertyhive_default_search_results_orderby', get_option( 'propertyhive_default_search_results_orderby' ) ); | |
| 469 | 493 | } |
| 470 | 494 | |
| 471 | 495 | $args = array( |
| 472 | - 'department' => $department !== '' ? $department : ( isset($_REQUEST['department']) ? $_REQUEST['department'] : '' ), | |
| 496 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public ordering control; explicit PHP arguments retain precedence. | |
| 497 | + 'department' => $department !== '' ? $department : ( isset($_REQUEST['department']) && is_string( $_REQUEST['department'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['department'] ) ) : '' ), | |
| 473 | 498 | 'orderby' => $orderby, |
| 474 | 499 | ); |
| 475 | 500 | |
| 476 | 501 | ph_get_template( 'search/orderby.php', $args ); |
| @@ -515,8 +540,9 @@ | ||
| 515 | 540 | } |
| 516 | 541 | |
| 517 | 542 | if ( get_option('propertyhive_off_market_behaviour', '') == 'redirect' ) |
| 518 | 543 | { |
| 544 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_redirect_off_market_properties; the established callable name is part of the plugin/extension API and must remain stable. | |
| 519 | 545 | function ph_redirect_off_market_properties() |
| 520 | 546 | { |
| 521 | 547 | // If we're viewing an off market property, redirect to the search form |
| 522 | 548 | if (is_singular('property')) |
| @@ -524,9 +550,9 @@ | ||
| 524 | 550 | if ( get_post_meta(get_the_ID(), '_on_market', TRUE) === '' ) |
| 525 | 551 | { |
| 526 | 552 | if ( !is_user_logged_in() || !current_user_can('administrator') && !current_user_can('editor') ) |
| 527 | 553 | { |
| 528 | - wp_redirect(get_permalink(ph_get_page_id('search_results')), 301); | |
| 554 | + wp_safe_redirect(get_permalink(ph_get_page_id('search_results')), 301); | |
| 529 | 555 | exit; |
| 530 | 556 | } |
| 531 | 557 | } |
| 532 | 558 | } |
| @@ -562,9 +588,9 @@ | ||
| 562 | 588 | { |
| 563 | 589 | $images[] = array( |
| 564 | 590 | 'title' => isset($photo['title']) ? $photo['title'] : '', |
| 565 | 591 | 'url' => isset($photo['url']) ? $photo['url'] : '', |
| 566 | - 'image' => '<img src="' . ( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . ( isset($photo['title']) ? $photo['title'] : '' ) . '">', | |
| 592 | + 'image' => '<img src="' . esc_url( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . esc_attr( isset($photo['title']) ? $photo['title'] : '' ) . '">', | |
| 567 | 593 | ); |
| 568 | 594 | } |
| 569 | 595 | } |
| 570 | 596 | else |
| @@ -617,9 +643,9 @@ | ||
| 617 | 643 | { |
| 618 | 644 | $images[] = array( |
| 619 | 645 | 'title' => isset($photo['title']) ? $photo['title'] : '', |
| 620 | 646 | 'url' => isset($photo['url']) ? $photo['url'] : '', |
| 621 | - 'image' => '<img src="' . ( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . ( isset($photo['title']) ? $photo['title'] : '' ) . '">', | |
| 647 | + 'image' => '<img src="' . esc_url( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . esc_attr( isset($photo['title']) ? $photo['title'] : '' ) . '">', | |
| 622 | 648 | ); |
| 623 | 649 | } |
| 624 | 650 | } |
| 625 | 651 | else |
| @@ -632,8 +658,9 @@ | ||
| 632 | 658 | { |
| 633 | 659 | $images[] = array( |
| 634 | 660 | 'title' => esc_attr( get_the_title( $gallery_attachment ) ), |
| 635 | 661 | 'url' => wp_get_attachment_url( $gallery_attachment ), |
| 662 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Existing public Property Hive extension hook single_property_small_thumbnail_size; changing the established name would detach installed callbacks. | |
| 636 | 663 | 'image' => wp_get_attachment_image( $gallery_attachment, apply_filters( 'single_property_small_thumbnail_size', 'thumbnail' ) ), |
| 637 | 664 | 'attachment_id' => $gallery_attachment, |
| 638 | 665 | ); |
| 639 | 666 | } |
| @@ -959,9 +986,9 @@ | ||
| 959 | 986 | if ( !empty( $floorplan_ids ) ) |
| 960 | 987 | { |
| 961 | 988 | foreach ($floorplan_ids as $floorplan_id) |
| 962 | 989 | { |
| 963 | - $label = 'Floorplan'; | |
| 990 | + $label = __( 'Floorplan', 'propertyhive' ); | |
| 964 | 991 | |
| 965 | 992 | $attachment_data = wp_prepare_attachment_for_js( $floorplan_id ); |
| 966 | 993 | if ( isset( $attachment_data['caption'] ) && $attachment_data['caption'] != '' ) |
| 967 | 994 | { |
| @@ -970,9 +997,9 @@ | ||
| 970 | 997 | |
| 971 | 998 | |
| 972 | 999 | $actions[] = array( |
| 973 | 1000 | 'href' => wp_get_attachment_url( $floorplan_id ), |
| 974 | - 'label' => __( $label, 'propertyhive' ), | |
| 1001 | + 'label' => $label, | |
| 975 | 1002 | 'class' => 'action-floorplans', |
| 976 | 1003 | 'attributes' => array( |
| 977 | 1004 | 'data-fancybox' => 'floorplans' |
| 978 | 1005 | ) |
| @@ -1115,9 +1142,9 @@ | ||
| 1115 | 1142 | } |
| 1116 | 1143 | |
| 1117 | 1144 | $actions[] = array( |
| 1118 | 1145 | 'href' => $virtual_tour['url'], |
| 1119 | - 'label' => __( $virtual_tour['label'], 'propertyhive' ), | |
| 1146 | + 'label' => $virtual_tour['label'], | |
| 1120 | 1147 | 'class' => 'action-virtual-tour', |
| 1121 | 1148 | 'attributes' => $attributes, |
| 1122 | 1149 | ); |
| 1123 | 1150 | } |
| @@ -1327,8 +1354,9 @@ | ||
| 1327 | 1354 | 'post_type' => 'viewing', |
| 1328 | 1355 | 'posts_per_page' => 1, |
| 1329 | 1356 | 'post_status' => 'publish', |
| 1330 | 1357 | 'fields' => 'ids', |
| 1358 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account page setup tests whether a contact has viewings; the first query and the owner-viewings query both use posts_per_page=1. The result is presence/count only and fields=ids; the owner query’s IN list is derived from the current owner’s properties. | |
| 1331 | 1359 | 'meta_query' => array( |
| 1332 | 1360 | array( |
| 1333 | 1361 | 'key' => '_applicant_contact_id', |
| 1334 | 1362 | 'value' => $contact->id |
| @@ -1355,8 +1383,9 @@ | ||
| 1355 | 1383 | 'post_type' => 'property', |
| 1356 | 1384 | 'nopaging' => true, |
| 1357 | 1385 | 'post_status' => 'publish', |
| 1358 | 1386 | 'fields' => 'ids', |
| 1387 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1359 | 1388 | 'meta_query' => array( |
| 1360 | 1389 | 'relation' => 'OR', |
| 1361 | 1390 | array( |
| 1362 | 1391 | 'key' => '_owner_contact_id', |
| @@ -1413,8 +1442,9 @@ | ||
| 1413 | 1442 | 'post_type' => 'viewing', |
| 1414 | 1443 | 'posts_per_page' => 1, |
| 1415 | 1444 | 'post_status' => 'publish', |
| 1416 | 1445 | 'fields' => 'ids', |
| 1446 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account page setup tests whether a contact has viewings; the first query and the owner-viewings query both use posts_per_page=1. The result is presence/count only and fields=ids; the owner query’s IN list is derived from the current owner’s properties. | |
| 1417 | 1447 | 'meta_query' => array( |
| 1418 | 1448 | array( |
| 1419 | 1449 | 'key' => '_property_id', |
| 1420 | 1450 | 'value' => $property_ids, |
| @@ -1443,9 +1473,9 @@ | ||
| 1443 | 1473 | );*/ |
| 1444 | 1474 | |
| 1445 | 1475 | $pages['logout'] = array( |
| 1446 | 1476 | 'name' => __( 'Logout', 'propertyhive' ), |
| 1447 | - 'href' => home_url() . '?logout=1' // Logout URL | |
| 1477 | + 'href' => wp_nonce_url( add_query_arg( 'logout', '1', home_url( '/' ) ), 'log-out' ) // Logout URL | |
| 1448 | 1478 | ); |
| 1449 | 1479 | |
| 1450 | 1480 | return $pages; |
| 1451 | 1481 | } |
| @@ -1590,9 +1620,11 @@ | ||
| 1590 | 1620 | 'fields' => 'ids', |
| 1591 | 1621 | 'orderby' => 'meta_value', |
| 1592 | 1622 | 'order' => 'DESC', |
| 1593 | 1623 | 'post_status' => 'publish', |
| 1624 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1594 | 1625 | 'meta_key' => '_start_date_time', |
| 1626 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1595 | 1627 | 'meta_query' => array( |
| 1596 | 1628 | array( |
| 1597 | 1629 | 'key' => '_applicant_contact_id', |
| 1598 | 1630 | 'value' => $contact->id |
| @@ -1603,9 +1635,9 @@ | ||
| 1603 | 1635 | // Do past viewings |
| 1604 | 1636 | $args2 = $args; |
| 1605 | 1637 | $args2['meta_query'][] = array( |
| 1606 | 1638 | 'key' => '_start_date_time', |
| 1607 | - 'value' => date("Y-m-d H:i:s"), | |
| 1639 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1608 | 1640 | 'compare' => '<=' |
| 1609 | 1641 | ); |
| 1610 | 1642 | |
| 1611 | 1643 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1626,9 +1658,9 @@ | ||
| 1626 | 1658 | // Do upcoming viewings |
| 1627 | 1659 | $args2 = $args; |
| 1628 | 1660 | $args2['meta_query'][] = array( |
| 1629 | 1661 | 'key' => '_start_date_time', |
| 1630 | - 'value' => date("Y-m-d H:i:s"), | |
| 1662 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1631 | 1663 | 'compare' => '>=' |
| 1632 | 1664 | ); |
| 1633 | 1665 | |
| 1634 | 1666 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1669,8 +1701,9 @@ | ||
| 1669 | 1701 | 'post_type' => 'property', |
| 1670 | 1702 | 'nopaging' => true, |
| 1671 | 1703 | 'post_status' => 'publish', |
| 1672 | 1704 | 'fields' => 'ids', |
| 1705 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1673 | 1706 | 'meta_query' => array( |
| 1674 | 1707 | 'relation' => 'OR', |
| 1675 | 1708 | array( |
| 1676 | 1709 | 'key' => '_owner_contact_id', |
| @@ -1722,8 +1755,9 @@ | ||
| 1722 | 1755 | 'post_type' => 'property', |
| 1723 | 1756 | 'nopaging' => true, |
| 1724 | 1757 | 'post_status' => 'publish', |
| 1725 | 1758 | 'fields' => 'ids', |
| 1759 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1726 | 1760 | 'meta_query' => array( |
| 1727 | 1761 | 'relation' => 'OR', |
| 1728 | 1762 | array( |
| 1729 | 1763 | 'key' => '_owner_contact_id', |
| @@ -1765,9 +1799,11 @@ | ||
| 1765 | 1799 | 'fields' => 'ids', |
| 1766 | 1800 | 'orderby' => 'meta_value', |
| 1767 | 1801 | 'order' => 'DESC', |
| 1768 | 1802 | 'post_status' => 'publish', |
| 1803 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1769 | 1804 | 'meta_key' => '_start_date_time', |
| 1805 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Account query is scoped to the current contact or their owned property IDs; these legacy relationship/date meta keys are required to return the complete account history using WordPress query APIs. | |
| 1770 | 1806 | 'meta_query' => array( |
| 1771 | 1807 | array( |
| 1772 | 1808 | 'key' => '_property_id', |
| 1773 | 1809 | 'value' => $property_ids, |
| @@ -1779,9 +1815,9 @@ | ||
| 1779 | 1815 | // Do past viewings |
| 1780 | 1816 | $args2 = $args; |
| 1781 | 1817 | $args2['meta_query'][] = array( |
| 1782 | 1818 | 'key' => '_start_date_time', |
| 1783 | - 'value' => date("Y-m-d H:i:s"), | |
| 1819 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1784 | 1820 | 'compare' => '<=' |
| 1785 | 1821 | ); |
| 1786 | 1822 | |
| 1787 | 1823 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1802,9 +1838,9 @@ | ||
| 1802 | 1838 | // Do upcoming viewings |
| 1803 | 1839 | $args2 = $args; |
| 1804 | 1840 | $args2['meta_query'][] = array( |
| 1805 | 1841 | 'key' => '_start_date_time', |
| 1806 | - 'value' => date("Y-m-d H:i:s"), | |
| 1842 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1807 | 1843 | 'compare' => '>=' |
| 1808 | 1844 | ); |
| 1809 | 1845 | |
| 1810 | 1846 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1841,8 +1877,9 @@ | ||
| 1841 | 1877 | } |
| 1842 | 1878 | } |
| 1843 | 1879 | |
| 1844 | 1880 | add_filter( 'loop_search_results_per_page', 'template_assistant_loop_search_results_per_page', 1 ); |
| 1881 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_loop_search_results_per_page; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1845 | 1882 | function template_assistant_loop_search_results_per_page( $cols ) |
| 1846 | 1883 | { |
| 1847 | 1884 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1848 | 1885 | |
| @@ -1854,8 +1891,9 @@ | ||
| 1854 | 1891 | return $cols; |
| 1855 | 1892 | } |
| 1856 | 1893 | |
| 1857 | 1894 | add_filter( 'loop_search_results_columns', 'template_assistant_search_result_columns', 1 ); |
| 1895 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_search_result_columns; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1858 | 1896 | function template_assistant_search_result_columns( $cols = 1 ) |
| 1859 | 1897 | { |
| 1860 | 1898 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1861 | 1899 | |
| @@ -1867,8 +1905,9 @@ | ||
| 1867 | 1905 | return 1; |
| 1868 | 1906 | } |
| 1869 | 1907 | |
| 1870 | 1908 | add_filter( 'post_class', 'template_assistant_property_columns_post_class', 20, 3 ); |
| 1909 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_property_columns_post_class; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1871 | 1910 | function template_assistant_property_columns_post_class( $classes, $class = '', $post_id = '' ) |
| 1872 | 1911 | { |
| 1873 | 1912 | if ( ! $post_id || get_post_type( $post_id ) !== 'property' ) |
| 1874 | 1913 | return $classes; |
| @@ -1893,8 +1932,9 @@ | ||
| 1893 | 1932 | return $classes; |
| 1894 | 1933 | } |
| 1895 | 1934 | |
| 1896 | 1935 | add_action( 'wp_head', 'load_template_assistant_styles' ); |
| 1936 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper load_template_assistant_styles; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1897 | 1937 | function load_template_assistant_styles() |
| 1898 | 1938 | { |
| 1899 | 1939 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1900 | 1940 | |
| @@ -1903,18 +1943,22 @@ | ||
| 1903 | 1943 | || |
| 1904 | 1944 | ( isset($current_settings['search_result_css_all_pages']) && $current_settings['search_result_css_all_pages'] == 'yes' ) |
| 1905 | 1945 | ) |
| 1906 | 1946 | { |
| 1907 | - if ( isset($current_settings['search_result_css']) ) | |
| 1947 | + if ( isset( $current_settings['search_result_css'] ) && is_string( $current_settings['search_result_css'] ) ) | |
| 1908 | 1948 | { |
| 1909 | - echo '<style type="text/css"> | |
| 1910 | - ' . $current_settings['search_result_css'] . ' | |
| 1911 | - </style>'; | |
| 1949 | + // Escape HTML's raw-text terminator without stripping valid stylesheet syntax. | |
| 1950 | + $css = preg_replace_callback( '~</style~i', static function( $match ) { | |
| 1951 | + return '<\\/' . substr( $match[0], 2 ); | |
| 1952 | + }, $current_settings['search_result_css'] ); | |
| 1953 | + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Privileged custom stylesheet: closing style tags are escaped above; HTML escaping would corrupt valid CSS strings and selectors. | |
| 1954 | + echo '<style type="text/css">' . $css . '</style>'; | |
| 1912 | 1955 | } |
| 1913 | 1956 | } |
| 1914 | 1957 | } |
| 1915 | 1958 | |
| 1916 | 1959 | add_filter( 'propertyhive_default_search_results_orderby', 'template_assistant_change_default_order' ); |
| 1960 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_change_default_order; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1917 | 1961 | function template_assistant_change_default_order( $orderby ) |
| 1918 | 1962 | { |
| 1919 | 1963 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1920 | 1964 | |
| @@ -1926,8 +1970,9 @@ | ||
| 1926 | 1970 | return $orderby; |
| 1927 | 1971 | } |
| 1928 | 1972 | |
| 1929 | 1973 | add_filter( 'property_search_results_thumbnail_size', 'template_assistant_search_result_image_size_changes' ); |
| 1974 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_search_result_image_size_changes; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1930 | 1975 | function template_assistant_search_result_image_size_changes( $image_size ) |
| 1931 | 1976 | { |
| 1932 | 1977 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1933 | 1978 | |
| @@ -1939,8 +1984,9 @@ | ||
| 1939 | 1984 | return $image_size; |
| 1940 | 1985 | } |
| 1941 | 1986 | |
| 1942 | 1987 | add_action( 'wp', 'template_assistant_search_result_field_changes' ); |
| 1988 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper template_assistant_search_result_field_changes; the established callable name is part of the plugin/extension API and must remain stable. | |
| 1943 | 1989 | function template_assistant_search_result_field_changes() |
| 1944 | 1990 | { |
| 1945 | 1991 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1946 | 1992 | |
| @@ -1976,19 +2022,19 @@ | ||
| 1976 | 2022 | break; |
| 1977 | 2023 | } |
| 1978 | 2024 | case "availability": |
| 1979 | 2025 | { |
| 1980 | - add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; echo '<div class="availability">' . $property->availability . '</div>'; }, $priority ); | |
| 2026 | + add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; echo '<div class="availability">' . esc_html( $property->availability ) . '</div>'; }, $priority ); | |
| 1981 | 2027 | break; |
| 1982 | 2028 | } |
| 1983 | 2029 | case "property_type": |
| 1984 | 2030 | { |
| 1985 | - add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; echo '<div class="property-type">' . $property->property_type . '</div>'; }, $priority ); | |
| 2031 | + add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; echo '<div class="property-type">' . esc_html( $property->property_type ) . '</div>'; }, $priority ); | |
| 1986 | 2032 | break; |
| 1987 | 2033 | } |
| 1988 | 2034 | case "available_date": |
| 1989 | 2035 | { |
| 1990 | - add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; if ( $property->department == 'residential-lettings' && $property->get_available_date() != '' ) { echo '<div class="available-date">' . $property->get_available_date() . '</div>'; } }, $priority ); | |
| 2036 | + add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; if ( $property->department == 'residential-lettings' && $property->get_available_date() != '' ) { echo '<div class="available-date">' . esc_html( $property->get_available_date() ) . '</div>'; } }, $priority ); | |
| 1991 | 2037 | break; |
| 1992 | 2038 | } |
| 1993 | 2039 | case "rooms": |
| 1994 | 2040 | { |
| @@ -1997,11 +2043,11 @@ | ||
| 1997 | 2043 | |
| 1998 | 2044 | if ( ($property->bedrooms != '' && $property->bedrooms != '0') || ($property->bathrooms != '' && $property->bathrooms != '0') || ($property->reception_rooms != '' && $property->reception_rooms != '0') ) |
| 1999 | 2045 | { |
| 2000 | 2046 | echo '<div class="rooms">'; |
| 2001 | - if ( $property->bedrooms != '' && $property->bedrooms != '0' ) { echo '<div class="room room-bedrooms"><span class="room-count">' . $property->bedrooms . '</span> <span class="room-label">Bedroom' . ( $property->bedrooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2002 | - if ( $property->bathrooms != '' && $property->bathrooms != '0' ) { echo '<div class="room room-bathrooms"><span class="room-count">' . $property->bathrooms . '</span> <span class="room-label">Bathroom' . ( $property->bathrooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2003 | - if ( $property->reception_rooms != '' && $property->reception_rooms != '0' ) { echo '<div class="room room-receptions"><span class="room-count">' . $property->reception_rooms . '</span> <span class="room-label">Reception' . ( $property->reception_rooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2047 | + if ( $property->bedrooms != '' && $property->bedrooms != '0' ) { echo '<div class="room room-bedrooms"><span class="room-count">' . esc_html( $property->bedrooms ) . '</span> <span class="room-label">Bedroom' . ( $property->bedrooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2048 | + if ( $property->bathrooms != '' && $property->bathrooms != '0' ) { echo '<div class="room room-bathrooms"><span class="room-count">' . esc_html( $property->bathrooms ) . '</span> <span class="room-label">Bathroom' . ( $property->bathrooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2049 | + if ( $property->reception_rooms != '' && $property->reception_rooms != '0' ) { echo '<div class="room room-receptions"><span class="room-count">' . esc_html( $property->reception_rooms ) . '</span> <span class="room-label">Reception' . ( $property->reception_rooms != 1 ? 's' : '' ) . '</span></div>'; } | |
| 2004 | 2050 | echo '</div>'; |
| 2005 | 2051 | } |
| 2006 | 2052 | }, $priority ); |
| 2007 | 2053 | break; |
| @@ -2034,14 +2080,36 @@ | ||
| 2034 | 2080 | $value = is_array($value) ? implode(", ", $value) : $value; |
| 2035 | 2081 | |
| 2036 | 2082 | if ( $value != '' ) |
| 2037 | 2083 | { |
| 2038 | - echo '<div class="custom-field custom-field-' . sanitize_title(trim($custom_field, "_")) . '">' . $value . '</div>'; | |
| 2084 | + echo '<div class="custom-field custom-field-' . esc_attr( sanitize_title(trim($custom_field, "_")) ) . '">' . wp_kses_post( $value ) . '</div>'; | |
| 2039 | 2085 | } |
| 2040 | 2086 | } |
| 2041 | 2087 | } |
| 2042 | 2088 | } |
| 2043 | 2089 | |
| 2090 | +/** | |
| 2091 | + * Sanitize configurable flag CSS while retaining CSS color functions. | |
| 2092 | + */ | |
| 2093 | +function propertyhive_get_flag_custom_style( $settings ) { | |
| 2094 | + $css = ( isset( $settings['flag_position'] ) && is_string( $settings['flag_position'] ) ? $settings['flag_position'] : '' ); | |
| 2095 | + foreach ( array( 'flag_text_color' => 'color', 'flag_bg_color' => 'background' ) as $key => $property_name ) { | |
| 2096 | + if ( isset( $settings[ $key ] ) && is_string( $settings[ $key ] ) ) { | |
| 2097 | + $css .= ';' . $property_name . ':' . $settings[ $key ]; | |
| 2098 | + } | |
| 2099 | + } | |
| 2100 | + $allow_color = static function( $allowed, $declaration ) { | |
| 2101 | + // The only parentheses accepted here enclose an RGB/HSL color value. | |
| 2102 | + return $allowed || 1 === preg_match( '/^(?:color|background):\s*(?:rgba?|hsla?)\([0-9a-z\s.,%+\-\/]*\)(?:\s*!important)?$/i', $declaration ); | |
| 2103 | + }; | |
| 2104 | + add_filter( 'safecss_filter_attr_allow_css', $allow_color, 10, 2 ); | |
| 2105 | + try { | |
| 2106 | + return safecss_filter_attr( $css ); | |
| 2107 | + } finally { | |
| 2108 | + remove_filter( 'safecss_filter_attr_allow_css', $allow_color, 10 ); | |
| 2109 | + } | |
| 2110 | +} | |
| 2111 | + | |
| 2044 | 2112 | add_action( 'propertyhive_before_search_results_loop_item_title', 'propertyhive_add_flag' ); |
| 2045 | 2113 | function propertyhive_add_flag() |
| 2046 | 2114 | { |
| 2047 | 2115 | global $property; |
| @@ -2053,9 +2121,9 @@ | ||
| 2053 | 2121 | $flag = propertyhive_get_flag(); |
| 2054 | 2122 | |
| 2055 | 2123 | if ( $flag != '' ) |
| 2056 | 2124 | { |
| 2057 | - echo '<div class="flag flag-' . sanitize_title($flag) . '" style="position:absolute; text-transform:uppercase; font-size:13px; box-sizing:border-box; padding:7px 20px; ' . $current_settings['flag_position'] . '; color:' . $current_settings['flag_text_color'] . '; background:' . $current_settings['flag_bg_color'] . ';">' . $flag . '</div>'; | |
| 2125 | + echo '<div class="flag flag-' . esc_attr( sanitize_title($flag) ) . '" style="' . esc_attr( 'position:absolute; text-transform:uppercase; font-size:13px; box-sizing:border-box; padding:7px 20px; ' . propertyhive_get_flag_custom_style( $current_settings ) ) . '">' . esc_html( $flag ) . '</div>'; | |
| 2058 | 2126 | } |
| 2059 | 2127 | } |
| 2060 | 2128 | } |
| 2061 | 2129 | |
| @@ -2071,9 +2139,9 @@ | ||
| 2071 | 2139 | $flag = propertyhive_get_flag(); |
| 2072 | 2140 | |
| 2073 | 2141 | if ( $flag != '' ) |
| 2074 | 2142 | { |
| 2075 | - echo '<div class="flag flag-' . sanitize_title($flag) . '" style="position:absolute; z-index:99; text-transform:uppercase; font-size:13px; box-sizing:border-box; padding:7px 20px; ' . $current_settings['flag_position'] . '; color:' . $current_settings['flag_text_color'] . '; background:' . $current_settings['flag_bg_color'] . ';">' . $flag . '</div>'; | |
| 2143 | + echo '<div class="flag flag-' . esc_attr( sanitize_title($flag) ) . '" style="' . esc_attr( 'position:absolute; z-index:99; text-transform:uppercase; font-size:13px; box-sizing:border-box; padding:7px 20px; ' . propertyhive_get_flag_custom_style( $current_settings ) ) . '">' . esc_html( $flag ) . '</div>'; | |
| 2076 | 2144 | } |
| 2077 | 2145 | } |
| 2078 | 2146 | } |
| 2079 | 2147 | |