PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 1.4.62 All 260 releases
← All changes | includes/ph-template-functions.php +86 -28 2.2.32.3.0 View file →
@@ -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