PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
2.3.1 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 All 261 releases
← All changes | includes/ph-template-functions.php +105 -37 2.2.22.3.1 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
@@ -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: &ldquo;%s&rdquo;', 'propertyhive' ), get_search_query() );
218 + if ( is_search() )
219 + {
220 + $page_title = sprintf(
221 + /* translators: %s: search query */
222 + __( 'Search Results: &ldquo;%s&rdquo;', 'propertyhive' ),
223 + get_search_query()
224 + );
211 225
212 226 if ( get_query_var( 'paged' ) )
213 - $page_title .= sprintf( __( '&nbsp;&ndash; Page %s', 'propertyhive' ), get_query_var( 'paged' ) );
227 + {
228 + $page_title .= sprintf(
229 + /* translators: %s: page number */
230 + __( '&nbsp;&ndash; 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