| @@ -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 | |
| @@ -232,11 +241,12 @@ | ||
| 232 | 241 | $search_results_page_id = ph_get_page_id( 'search_results' ); |
| 233 | 242 | $page_title = get_the_title( $search_results_page_id ); |
| 234 | 243 | } |
| 235 | 244 | |
| 236 | - $page_title = apply_filters( 'propertyhive_page_title', $page_title ); | |
| 245 | + $page_title = apply_filters( 'propertyhive_page_title', wp_kses_post( $page_title ) ); | |
| 237 | 246 | |
| 238 | 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. | |
| 239 | 249 | echo $page_title; |
| 240 | 250 | else |
| 241 | 251 | return $page_title; |
| 242 | 252 | } |
| @@ -254,8 +264,9 @@ | ||
| 254 | 264 | function propertyhive_property_loop_start( $echo = true ) { |
| 255 | 265 | ob_start(); |
| 256 | 266 | ph_get_template( 'search/loop-start.php' ); |
| 257 | 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. | |
| 258 | 269 | echo ob_get_clean(); |
| 259 | 270 | else |
| 260 | 271 | return ob_get_clean(); |
| 261 | 272 | } |
| @@ -274,8 +285,9 @@ | ||
| 274 | 285 | |
| 275 | 286 | ph_get_template( 'search/loop-end.php' ); |
| 276 | 287 | |
| 277 | 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. | |
| 278 | 290 | echo ob_get_clean(); |
| 279 | 291 | else |
| 280 | 292 | return ob_get_clean(); |
| 281 | 293 | } |
| @@ -290,8 +302,9 @@ | ||
| 290 | 302 | * @subpackage Loop |
| 291 | 303 | * @return void |
| 292 | 304 | */ |
| 293 | 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. | |
| 294 | 307 | echo propertyhive_get_property_thumbnail( apply_filters( 'property_search_results_thumbnail_size', 'medium' ) ); |
| 295 | 308 | } |
| 296 | 309 | } |
| 297 | 310 | |
| @@ -312,9 +325,9 @@ | ||
| 312 | 325 | |
| 313 | 326 | $photo_url = $property->get_main_photo_src( $size ); |
| 314 | 327 | |
| 315 | 328 | if ($photo_url !== FALSE) |
| 316 | - 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 ) . '">'; | |
| 317 | 330 | |
| 318 | 331 | if ( ph_placeholder_img_src() ) |
| 319 | 332 | return ph_placeholder_img( $size ); |
| 320 | 333 | } |
| @@ -474,13 +487,15 @@ | ||
| 474 | 487 | function propertyhive_catalog_ordering( $department = '', $orderby = '' ) { |
| 475 | 488 | |
| 476 | 489 | if ( $orderby === '' ) |
| 477 | 490 | { |
| 478 | - $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' ) ); | |
| 479 | 493 | } |
| 480 | 494 | |
| 481 | 495 | $args = array( |
| 482 | - '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'] ) ) : '' ), | |
| 483 | 498 | 'orderby' => $orderby, |
| 484 | 499 | ); |
| 485 | 500 | |
| 486 | 501 | ph_get_template( 'search/orderby.php', $args ); |
| @@ -525,8 +540,9 @@ | ||
| 525 | 540 | } |
| 526 | 541 | |
| 527 | 542 | if ( get_option('propertyhive_off_market_behaviour', '') == 'redirect' ) |
| 528 | 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. | |
| 529 | 545 | function ph_redirect_off_market_properties() |
| 530 | 546 | { |
| 531 | 547 | // If we're viewing an off market property, redirect to the search form |
| 532 | 548 | if (is_singular('property')) |
| @@ -534,9 +550,9 @@ | ||
| 534 | 550 | if ( get_post_meta(get_the_ID(), '_on_market', TRUE) === '' ) |
| 535 | 551 | { |
| 536 | 552 | if ( !is_user_logged_in() || !current_user_can('administrator') && !current_user_can('editor') ) |
| 537 | 553 | { |
| 538 | - wp_redirect(get_permalink(ph_get_page_id('search_results')), 301); | |
| 554 | + wp_safe_redirect(get_permalink(ph_get_page_id('search_results')), 301); | |
| 539 | 555 | exit; |
| 540 | 556 | } |
| 541 | 557 | } |
| 542 | 558 | } |
| @@ -572,9 +588,9 @@ | ||
| 572 | 588 | { |
| 573 | 589 | $images[] = array( |
| 574 | 590 | 'title' => isset($photo['title']) ? $photo['title'] : '', |
| 575 | 591 | 'url' => isset($photo['url']) ? $photo['url'] : '', |
| 576 | - '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'] : '' ) . '">', | |
| 577 | 593 | ); |
| 578 | 594 | } |
| 579 | 595 | } |
| 580 | 596 | else |
| @@ -627,9 +643,9 @@ | ||
| 627 | 643 | { |
| 628 | 644 | $images[] = array( |
| 629 | 645 | 'title' => isset($photo['title']) ? $photo['title'] : '', |
| 630 | 646 | 'url' => isset($photo['url']) ? $photo['url'] : '', |
| 631 | - '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'] : '' ) . '">', | |
| 632 | 648 | ); |
| 633 | 649 | } |
| 634 | 650 | } |
| 635 | 651 | else |
| @@ -642,8 +658,9 @@ | ||
| 642 | 658 | { |
| 643 | 659 | $images[] = array( |
| 644 | 660 | 'title' => esc_attr( get_the_title( $gallery_attachment ) ), |
| 645 | 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. | |
| 646 | 663 | 'image' => wp_get_attachment_image( $gallery_attachment, apply_filters( 'single_property_small_thumbnail_size', 'thumbnail' ) ), |
| 647 | 664 | 'attachment_id' => $gallery_attachment, |
| 648 | 665 | ); |
| 649 | 666 | } |
| @@ -969,9 +986,9 @@ | ||
| 969 | 986 | if ( !empty( $floorplan_ids ) ) |
| 970 | 987 | { |
| 971 | 988 | foreach ($floorplan_ids as $floorplan_id) |
| 972 | 989 | { |
| 973 | - $label = 'Floorplan'; | |
| 990 | + $label = __( 'Floorplan', 'propertyhive' ); | |
| 974 | 991 | |
| 975 | 992 | $attachment_data = wp_prepare_attachment_for_js( $floorplan_id ); |
| 976 | 993 | if ( isset( $attachment_data['caption'] ) && $attachment_data['caption'] != '' ) |
| 977 | 994 | { |
| @@ -980,9 +997,9 @@ | ||
| 980 | 997 | |
| 981 | 998 | |
| 982 | 999 | $actions[] = array( |
| 983 | 1000 | 'href' => wp_get_attachment_url( $floorplan_id ), |
| 984 | - 'label' => __( $label, 'propertyhive' ), | |
| 1001 | + 'label' => $label, | |
| 985 | 1002 | 'class' => 'action-floorplans', |
| 986 | 1003 | 'attributes' => array( |
| 987 | 1004 | 'data-fancybox' => 'floorplans' |
| 988 | 1005 | ) |
| @@ -1125,9 +1142,9 @@ | ||
| 1125 | 1142 | } |
| 1126 | 1143 | |
| 1127 | 1144 | $actions[] = array( |
| 1128 | 1145 | 'href' => $virtual_tour['url'], |
| 1129 | - 'label' => __( $virtual_tour['label'], 'propertyhive' ), | |
| 1146 | + 'label' => $virtual_tour['label'], | |
| 1130 | 1147 | 'class' => 'action-virtual-tour', |
| 1131 | 1148 | 'attributes' => $attributes, |
| 1132 | 1149 | ); |
| 1133 | 1150 | } |
| @@ -1337,8 +1354,9 @@ | ||
| 1337 | 1354 | 'post_type' => 'viewing', |
| 1338 | 1355 | 'posts_per_page' => 1, |
| 1339 | 1356 | 'post_status' => 'publish', |
| 1340 | 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. | |
| 1341 | 1359 | 'meta_query' => array( |
| 1342 | 1360 | array( |
| 1343 | 1361 | 'key' => '_applicant_contact_id', |
| 1344 | 1362 | 'value' => $contact->id |
| @@ -1365,8 +1383,9 @@ | ||
| 1365 | 1383 | 'post_type' => 'property', |
| 1366 | 1384 | 'nopaging' => true, |
| 1367 | 1385 | 'post_status' => 'publish', |
| 1368 | 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. | |
| 1369 | 1388 | 'meta_query' => array( |
| 1370 | 1389 | 'relation' => 'OR', |
| 1371 | 1390 | array( |
| 1372 | 1391 | 'key' => '_owner_contact_id', |
| @@ -1423,8 +1442,9 @@ | ||
| 1423 | 1442 | 'post_type' => 'viewing', |
| 1424 | 1443 | 'posts_per_page' => 1, |
| 1425 | 1444 | 'post_status' => 'publish', |
| 1426 | 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. | |
| 1427 | 1447 | 'meta_query' => array( |
| 1428 | 1448 | array( |
| 1429 | 1449 | 'key' => '_property_id', |
| 1430 | 1450 | 'value' => $property_ids, |
| @@ -1453,9 +1473,9 @@ | ||
| 1453 | 1473 | );*/ |
| 1454 | 1474 | |
| 1455 | 1475 | $pages['logout'] = array( |
| 1456 | 1476 | 'name' => __( 'Logout', 'propertyhive' ), |
| 1457 | - 'href' => home_url() . '?logout=1' // Logout URL | |
| 1477 | + 'href' => wp_nonce_url( add_query_arg( 'logout', '1', home_url( '/' ) ), 'log-out' ) // Logout URL | |
| 1458 | 1478 | ); |
| 1459 | 1479 | |
| 1460 | 1480 | return $pages; |
| 1461 | 1481 | } |
| @@ -1600,9 +1620,11 @@ | ||
| 1600 | 1620 | 'fields' => 'ids', |
| 1601 | 1621 | 'orderby' => 'meta_value', |
| 1602 | 1622 | 'order' => 'DESC', |
| 1603 | 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. | |
| 1604 | 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. | |
| 1605 | 1627 | 'meta_query' => array( |
| 1606 | 1628 | array( |
| 1607 | 1629 | 'key' => '_applicant_contact_id', |
| 1608 | 1630 | 'value' => $contact->id |
| @@ -1613,9 +1635,9 @@ | ||
| 1613 | 1635 | // Do past viewings |
| 1614 | 1636 | $args2 = $args; |
| 1615 | 1637 | $args2['meta_query'][] = array( |
| 1616 | 1638 | 'key' => '_start_date_time', |
| 1617 | - 'value' => date("Y-m-d H:i:s"), | |
| 1639 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1618 | 1640 | 'compare' => '<=' |
| 1619 | 1641 | ); |
| 1620 | 1642 | |
| 1621 | 1643 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1636,9 +1658,9 @@ | ||
| 1636 | 1658 | // Do upcoming viewings |
| 1637 | 1659 | $args2 = $args; |
| 1638 | 1660 | $args2['meta_query'][] = array( |
| 1639 | 1661 | 'key' => '_start_date_time', |
| 1640 | - 'value' => date("Y-m-d H:i:s"), | |
| 1662 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1641 | 1663 | 'compare' => '>=' |
| 1642 | 1664 | ); |
| 1643 | 1665 | |
| 1644 | 1666 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1679,8 +1701,9 @@ | ||
| 1679 | 1701 | 'post_type' => 'property', |
| 1680 | 1702 | 'nopaging' => true, |
| 1681 | 1703 | 'post_status' => 'publish', |
| 1682 | 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. | |
| 1683 | 1706 | 'meta_query' => array( |
| 1684 | 1707 | 'relation' => 'OR', |
| 1685 | 1708 | array( |
| 1686 | 1709 | 'key' => '_owner_contact_id', |
| @@ -1732,8 +1755,9 @@ | ||
| 1732 | 1755 | 'post_type' => 'property', |
| 1733 | 1756 | 'nopaging' => true, |
| 1734 | 1757 | 'post_status' => 'publish', |
| 1735 | 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. | |
| 1736 | 1760 | 'meta_query' => array( |
| 1737 | 1761 | 'relation' => 'OR', |
| 1738 | 1762 | array( |
| 1739 | 1763 | 'key' => '_owner_contact_id', |
| @@ -1775,9 +1799,11 @@ | ||
| 1775 | 1799 | 'fields' => 'ids', |
| 1776 | 1800 | 'orderby' => 'meta_value', |
| 1777 | 1801 | 'order' => 'DESC', |
| 1778 | 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. | |
| 1779 | 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. | |
| 1780 | 1806 | 'meta_query' => array( |
| 1781 | 1807 | array( |
| 1782 | 1808 | 'key' => '_property_id', |
| 1783 | 1809 | 'value' => $property_ids, |
| @@ -1789,9 +1815,9 @@ | ||
| 1789 | 1815 | // Do past viewings |
| 1790 | 1816 | $args2 = $args; |
| 1791 | 1817 | $args2['meta_query'][] = array( |
| 1792 | 1818 | 'key' => '_start_date_time', |
| 1793 | - 'value' => date("Y-m-d H:i:s"), | |
| 1819 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1794 | 1820 | 'compare' => '<=' |
| 1795 | 1821 | ); |
| 1796 | 1822 | |
| 1797 | 1823 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1812,9 +1838,9 @@ | ||
| 1812 | 1838 | // Do upcoming viewings |
| 1813 | 1839 | $args2 = $args; |
| 1814 | 1840 | $args2['meta_query'][] = array( |
| 1815 | 1841 | 'key' => '_start_date_time', |
| 1816 | - 'value' => date("Y-m-d H:i:s"), | |
| 1842 | + 'value' => gmdate("Y-m-d H:i:s"), | |
| 1817 | 1843 | 'compare' => '>=' |
| 1818 | 1844 | ); |
| 1819 | 1845 | |
| 1820 | 1846 | $viewings_query = new WP_Query( $args2 ); |
| @@ -1851,8 +1877,9 @@ | ||
| 1851 | 1877 | } |
| 1852 | 1878 | } |
| 1853 | 1879 | |
| 1854 | 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. | |
| 1855 | 1882 | function template_assistant_loop_search_results_per_page( $cols ) |
| 1856 | 1883 | { |
| 1857 | 1884 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1858 | 1885 | |
| @@ -1864,8 +1891,9 @@ | ||
| 1864 | 1891 | return $cols; |
| 1865 | 1892 | } |
| 1866 | 1893 | |
| 1867 | 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. | |
| 1868 | 1896 | function template_assistant_search_result_columns( $cols = 1 ) |
| 1869 | 1897 | { |
| 1870 | 1898 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1871 | 1899 | |
| @@ -1877,8 +1905,9 @@ | ||
| 1877 | 1905 | return 1; |
| 1878 | 1906 | } |
| 1879 | 1907 | |
| 1880 | 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. | |
| 1881 | 1910 | function template_assistant_property_columns_post_class( $classes, $class = '', $post_id = '' ) |
| 1882 | 1911 | { |
| 1883 | 1912 | if ( ! $post_id || get_post_type( $post_id ) !== 'property' ) |
| 1884 | 1913 | return $classes; |
| @@ -1903,8 +1932,9 @@ | ||
| 1903 | 1932 | return $classes; |
| 1904 | 1933 | } |
| 1905 | 1934 | |
| 1906 | 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. | |
| 1907 | 1937 | function load_template_assistant_styles() |
| 1908 | 1938 | { |
| 1909 | 1939 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1910 | 1940 | |
| @@ -1913,18 +1943,22 @@ | ||
| 1913 | 1943 | || |
| 1914 | 1944 | ( isset($current_settings['search_result_css_all_pages']) && $current_settings['search_result_css_all_pages'] == 'yes' ) |
| 1915 | 1945 | ) |
| 1916 | 1946 | { |
| 1917 | - if ( isset($current_settings['search_result_css']) ) | |
| 1947 | + if ( isset( $current_settings['search_result_css'] ) && is_string( $current_settings['search_result_css'] ) ) | |
| 1918 | 1948 | { |
| 1919 | - echo '<style type="text/css"> | |
| 1920 | - ' . $current_settings['search_result_css'] . ' | |
| 1921 | - </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>'; | |
| 1922 | 1955 | } |
| 1923 | 1956 | } |
| 1924 | 1957 | } |
| 1925 | 1958 | |
| 1926 | 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. | |
| 1927 | 1961 | function template_assistant_change_default_order( $orderby ) |
| 1928 | 1962 | { |
| 1929 | 1963 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1930 | 1964 | |
| @@ -1936,8 +1970,9 @@ | ||
| 1936 | 1970 | return $orderby; |
| 1937 | 1971 | } |
| 1938 | 1972 | |
| 1939 | 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. | |
| 1940 | 1975 | function template_assistant_search_result_image_size_changes( $image_size ) |
| 1941 | 1976 | { |
| 1942 | 1977 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1943 | 1978 | |
| @@ -1949,8 +1984,9 @@ | ||
| 1949 | 1984 | return $image_size; |
| 1950 | 1985 | } |
| 1951 | 1986 | |
| 1952 | 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. | |
| 1953 | 1989 | function template_assistant_search_result_field_changes() |
| 1954 | 1990 | { |
| 1955 | 1991 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1956 | 1992 | |
| @@ -1986,19 +2022,19 @@ | ||
| 1986 | 2022 | break; |
| 1987 | 2023 | } |
| 1988 | 2024 | case "availability": |
| 1989 | 2025 | { |
| 1990 | - 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 ); | |
| 1991 | 2027 | break; |
| 1992 | 2028 | } |
| 1993 | 2029 | case "property_type": |
| 1994 | 2030 | { |
| 1995 | - 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 ); | |
| 1996 | 2032 | break; |
| 1997 | 2033 | } |
| 1998 | 2034 | case "available_date": |
| 1999 | 2035 | { |
| 2000 | - 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 ); | |
| 2001 | 2037 | break; |
| 2002 | 2038 | } |
| 2003 | 2039 | case "rooms": |
| 2004 | 2040 | { |
| @@ -2007,11 +2043,11 @@ | ||
| 2007 | 2043 | |
| 2008 | 2044 | if ( ($property->bedrooms != '' && $property->bedrooms != '0') || ($property->bathrooms != '' && $property->bathrooms != '0') || ($property->reception_rooms != '' && $property->reception_rooms != '0') ) |
| 2009 | 2045 | { |
| 2010 | 2046 | echo '<div class="rooms">'; |
| 2011 | - 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>'; } | |
| 2012 | - 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>'; } | |
| 2013 | - 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>'; } | |
| 2014 | 2050 | echo '</div>'; |
| 2015 | 2051 | } |
| 2016 | 2052 | }, $priority ); |
| 2017 | 2053 | break; |
| @@ -2044,14 +2080,36 @@ | ||
| 2044 | 2080 | $value = is_array($value) ? implode(", ", $value) : $value; |
| 2045 | 2081 | |
| 2046 | 2082 | if ( $value != '' ) |
| 2047 | 2083 | { |
| 2048 | - 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>'; | |
| 2049 | 2085 | } |
| 2050 | 2086 | } |
| 2051 | 2087 | } |
| 2052 | 2088 | } |
| 2053 | 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 | + | |
| 2054 | 2112 | add_action( 'propertyhive_before_search_results_loop_item_title', 'propertyhive_add_flag' ); |
| 2055 | 2113 | function propertyhive_add_flag() |
| 2056 | 2114 | { |
| 2057 | 2115 | global $property; |
| @@ -2063,9 +2121,9 @@ | ||
| 2063 | 2121 | $flag = propertyhive_get_flag(); |
| 2064 | 2122 | |
| 2065 | 2123 | if ( $flag != '' ) |
| 2066 | 2124 | { |
| 2067 | - 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>'; | |
| 2068 | 2126 | } |
| 2069 | 2127 | } |
| 2070 | 2128 | } |
| 2071 | 2129 | |
| @@ -2081,9 +2139,9 @@ | ||
| 2081 | 2139 | $flag = propertyhive_get_flag(); |
| 2082 | 2140 | |
| 2083 | 2141 | if ( $flag != '' ) |
| 2084 | 2142 | { |
| 2085 | - 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>'; | |
| 2086 | 2144 | } |
| 2087 | 2145 | } |
| 2088 | 2146 | } |
| 2089 | 2147 | |