PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
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 +745 -69 1.4.602.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
@@ -44,9 +50,9 @@
44 50 if ( is_post_type_archive( 'property' ) || is_singular( 'property' ) ) {
45 51
46 52 $feed = get_post_type_archive_feed_link( 'property' );
47 53
48 - echo '<link rel="alternate" type="application/rss+xml" title="' . __( 'Latest Properties', 'propertyhive' ) . '" href="' . esc_attr( $feed ) . '" />';
54 + echo '<link rel="alternate" type="application/rss+xml" title="' . esc_attr(__( 'Latest Properties', 'propertyhive' )) . '" href="' . esc_url( $feed ) . '" />';
49 55
50 56 }
51 57 }
52 58
@@ -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,9 +80,12 @@
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 ) {
86 + global $wp_query;
87 +
78 88 $classes = (array) $classes;
79 89
80 90 if ( is_propertyhive() ) {
81 91 $classes[] = 'propertyhive';
@@ -81,8 +91,48 @@
81 91 $classes[] = 'propertyhive';
82 92 $classes[] = 'propertyhive-page';
83 93 }
84 94
95 + // If we're on a single property, add body classes for taxonomy fields and department
96 + if ( is_single() )
97 + {
98 + $post = $wp_query->get_queried_object();
99 + if ( $post->post_type === 'property' )
100 + {
101 + // Duplication of the code in post-template-php that is done for search results posts in get_post_class()
102 + // All public taxonomies.
103 + $taxonomies = get_taxonomies( array( 'public' => true ) );
104 + foreach ( (array) $taxonomies as $taxonomy ) {
105 + if ( is_object_in_taxonomy( $post->post_type, $taxonomy ) ) {
106 + foreach ( (array) get_the_terms( $post->ID, $taxonomy ) as $term ) {
107 + if ( empty( $term->slug ) ) {
108 + continue;
109 + }
110 +
111 + $term_class = sanitize_html_class( $term->slug, $term->term_id );
112 + if ( is_numeric( $term_class ) || ! trim( $term_class, '-' ) ) {
113 + $term_class = $term->term_id;
114 + }
115 +
116 + // 'post_tag' uses the 'tag' prefix for backward compatibility.
117 + if ( 'post_tag' == $taxonomy ) {
118 + $classes[] = 'tag-' . $term_class;
119 + } else {
120 + $classes[] = sanitize_html_class( $taxonomy . '-' . $term_class, $taxonomy . '-' . $term->term_id );
121 + }
122 + }
123 + }
124 + }
125 + //
126 +
127 + $property = get_property( $post->ID );
128 + $classes[] = 'department-' . $property->department;
129 + $classes[] = 'on-market-' . ( $property->on_market == 'yes' ? 'yes' : 'no' );
130 + $classes[] = 'featured-' . ( $property->featured == 'yes' ? 'yes' : 'no' );
131 + $classes[] = 'office-' . $property->office_id;
132 + }
133 + }
134 +
85 135 return array_unique( $classes );
86 136 }
87 137
88 138 /**
@@ -93,8 +143,9 @@
93 143 * @param string|array $class
94 144 * @param int $post_id
95 145 * @return array
96 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.
97 148 function ph_property_post_class( $classes, $class = '', $post_id = '' ) {
98 149 if ( ! $post_id || get_post_type( $post_id ) !== 'property' )
99 150 return $classes;
100 151
@@ -116,9 +167,12 @@
116 167 unset( $classes[ $key ] );
117 168 }
118 169 $classes[] = 'property';
119 170 $classes[] = 'department-' . $property->department;
171 + $classes[] = 'office-' . $property->office_id;
120 172
173 + $classes = array_values($classes);
174 +
121 175 return $classes;
122 176 }
123 177
124 178 /** Global ****************************************************************/
@@ -160,28 +214,39 @@
160 214 * @return string
161 215 */
162 216 function propertyhive_page_title( $echo = true ) {
163 217
164 - if ( is_search() ) {
165 - $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 + );
166 225
167 226 if ( get_query_var( 'paged' ) )
168 - $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 + }
169 234
170 - } elseif ( is_tax() ) {
171 -
235 + }elseif ( is_tax() )
236 + {
172 237 $page_title = single_term_title( "", false );
173 -
174 - } else {
175 -
238 + }
239 + else
240 + {
176 241 $search_results_page_id = ph_get_page_id( 'search_results' );
177 242 $page_title = get_the_title( $search_results_page_id );
178 -
179 243 }
180 244
181 - $page_title = apply_filters( 'propertyhive_page_title', $page_title );
245 + $page_title = apply_filters( 'propertyhive_page_title', wp_kses_post( $page_title ) );
182 246
183 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.
184 249 echo $page_title;
185 250 else
186 251 return $page_title;
187 252 }
@@ -199,8 +264,9 @@
199 264 function propertyhive_property_loop_start( $echo = true ) {
200 265 ob_start();
201 266 ph_get_template( 'search/loop-start.php' );
202 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.
203 269 echo ob_get_clean();
204 270 else
205 271 return ob_get_clean();
206 272 }
@@ -219,8 +285,9 @@
219 285
220 286 ph_get_template( 'search/loop-end.php' );
221 287
222 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.
223 290 echo ob_get_clean();
224 291 else
225 292 return ob_get_clean();
226 293 }
@@ -235,9 +302,10 @@
235 302 * @subpackage Loop
236 303 * @return void
237 304 */
238 305 function propertyhive_template_loop_property_thumbnail() {
239 - echo propertyhive_get_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.
307 + echo propertyhive_get_property_thumbnail( apply_filters( 'property_search_results_thumbnail_size', 'medium' ) );
240 308 }
241 309 }
242 310
243 311 if ( ! function_exists( 'propertyhive_get_property_thumbnail' ) ) {
@@ -257,9 +325,9 @@
257 325
258 326 $photo_url = $property->get_main_photo_src( $size );
259 327
260 328 if ($photo_url !== FALSE)
261 - 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 ) . '">';
262 330
263 331 if ( ph_placeholder_img_src() )
264 332 return ph_placeholder_img( $size );
265 333 }
@@ -274,9 +342,14 @@
274 342 * @subpackage Loop
275 343 * @return void
276 344 */
277 345 function propertyhive_template_loop_floor_area() {
278 - ph_get_template( 'search/floor-area.php' );
346 + global $property;
347 +
348 + if ( $property->department == 'commercial' || ph_get_custom_department_based_on( $property->department ) == 'commercial' )
349 + {
350 + ph_get_template( 'search/floor-area.php' );
351 + }
279 352 }
280 353 }
281 354
282 355 if ( ! function_exists( 'propertyhive_template_loop_price' ) ) {
@@ -311,10 +384,23 @@
311 384 $fees = nl2br(get_option('propertyhive_lettings_fees_commercial', ''));
312 385 }
313 386 }
314 387
388 + $price_qualifier = '';
389 + if (
390 + (
391 + $property->department == 'residential-sales' ||
392 + ph_get_custom_department_based_on($property->department) == 'residential-sales' ||
393 + $property->department == 'commercial' ||
394 + ph_get_custom_department_based_on($property->department) == 'commercial'
395 + ) &&
396 + $property->price_qualifier != ''
397 + )
398 + {
399 + $price_qualifier = $property->price_qualifier;
400 + }
315 401
316 - ph_get_template( 'search/price.php', array( 'fees' => $fees ) );
402 + ph_get_template( 'search/price.php', array( 'price_qualifier' => $price_qualifier, 'fees' => $fees ) );
317 403 }
318 404 }
319 405
320 406 if ( ! function_exists( 'propertyhive_template_loop_summary' ) ) {
@@ -367,10 +453,26 @@
367 453 * @access public
368 454 * @subpackage Loop
369 455 * @return void
370 456 */
371 - function propertyhive_result_count() {
372 - ph_get_template( 'search/result-count.php' );
457 + function propertyhive_result_count( $paged = '', $per_page = null, $total = null, $first = null, $last = null ) {
458 + global $wp_query;
459 +
460 + $paged = $paged !== '' ? (int)$paged : max( 1, (int)$wp_query->get( 'paged' ) );
461 + $per_page = $per_page !== null ? (int)$per_page : max( 1, (int)$wp_query->get( 'posts_per_page' ) );
462 + $total = $total !== null ? (int)$total : (int)$wp_query->found_posts;
463 + $first = $first !== null ? (int)$first : ( $per_page * $paged ) - $per_page + 1;
464 + $last = $last !== null ? (int)$last : min( $total, $per_page * $paged );
465 +
466 + $args = array(
467 + 'paged' => $paged,
468 + 'per_page' => $per_page,
469 + 'total' => $total,
470 + 'first' => $first,
471 + 'last' => $last,
472 + );
473 +
474 + ph_get_template( 'search/result-count.php', $args );
373 475 }
374 476 }
375 477
376 478 if ( ! function_exists( 'propertyhive_catalog_ordering' ) ) {
@@ -381,12 +483,23 @@
381 483 * @access public
382 484 * @subpackage Loop
383 485 * @return void
384 486 */
385 - function propertyhive_catalog_ordering() {
386 - $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' ) );
487 + function propertyhive_catalog_ordering( $department = '', $orderby = '' ) {
387 488
388 - ph_get_template( 'search/orderby.php', array( 'orderby' => $orderby ) );
489 + if ( $orderby === '' )
490 + {
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' ) );
493 + }
494 +
495 + $args = array(
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'] ) ) : '' ),
498 + 'orderby' => $orderby,
499 + );
500 +
501 + ph_get_template( 'search/orderby.php', $args );
389 502 }
390 503 }
391 504
392 505 if ( ! function_exists( 'propertyhive_pagination' ) ) {
@@ -397,10 +510,17 @@
397 510 * @access public
398 511 * @subpackage Loop
399 512 * @return void
400 513 */
401 - function propertyhive_pagination() {
402 - ph_get_template( 'search/pagination.php' );
514 + function propertyhive_pagination( $max_num_pages = '' ) {
515 +
516 + global $wp_query;
517 +
518 + $args = array(
519 + 'max_num_pages' => $max_num_pages !== '' ? $max_num_pages : $wp_query->max_num_pages,
520 + );
521 +
522 + ph_get_template( 'search/pagination.php', $args );
403 523 }
404 524 }
405 525
406 526 /** Single Property ********************************************************/
@@ -418,8 +538,29 @@
418 538 ph_get_template( 'single-property/not-on-market.php' );
419 539 }
420 540 }
421 541
542 +if ( get_option('propertyhive_off_market_behaviour', '') == 'redirect' )
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.
545 + function ph_redirect_off_market_properties()
546 + {
547 + // If we're viewing an off market property, redirect to the search form
548 + if (is_singular('property'))
549 + {
550 + if ( get_post_meta(get_the_ID(), '_on_market', TRUE) === '' )
551 + {
552 + if ( !is_user_logged_in() || !current_user_can('administrator') && !current_user_can('editor') )
553 + {
554 + wp_safe_redirect(get_permalink(ph_get_page_id('search_results')), 301);
555 + exit;
556 + }
557 + }
558 + }
559 + }
560 + add_action( 'template_redirect', 'ph_redirect_off_market_properties', 5 );
561 +}
562 +
422 563 if ( ! function_exists( 'propertyhive_show_property_images' ) ) {
423 564
424 565 /**
425 566 * Output the property image before the single property summary.
@@ -427,9 +568,9 @@
427 568 * @access public
428 569 * @subpackage Property
429 570 * @return void
430 571 */
431 - function propertyhive_show_property_images()
572 + function propertyhive_show_property_images( $num_images = '' )
432 573 {
433 574 global $property;
434 575
435 576 $images = array();
@@ -437,14 +578,19 @@
437 578 {
438 579 $photo_urls = $property->_photo_urls;
439 580 if ( !is_array($photo_urls) ) { $photo_urls = array(); }
440 581
582 + if ( !empty($num_images) )
583 + {
584 + $photo_urls = array_slice($photo_urls, 0, $num_images);
585 + }
586 +
441 587 foreach ( $photo_urls as $photo )
442 588 {
443 589 $images[] = array(
444 590 'title' => isset($photo['title']) ? $photo['title'] : '',
445 591 'url' => isset($photo['url']) ? $photo['url'] : '',
446 - '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'] : '' ) . '">',
447 593 );
448 594 }
449 595 }
450 596 else
@@ -452,8 +598,13 @@
452 598 $gallery_attachments = $property->get_gallery_attachment_ids();
453 599
454 600 if ( !empty($gallery_attachments) )
455 601 {
602 + if ( !empty($num_images) )
603 + {
604 + $gallery_attachments = array_slice($gallery_attachments, 0, $num_images);
605 + }
606 +
456 607 foreach ($gallery_attachments as $gallery_attachment)
457 608 {
458 609 $images[] = array(
459 610 'title' => esc_attr( get_the_title( $gallery_attachment ) ),
@@ -492,9 +643,9 @@
492 643 {
493 644 $images[] = array(
494 645 'title' => isset($photo['title']) ? $photo['title'] : '',
495 646 'url' => isset($photo['url']) ? $photo['url'] : '',
496 - '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'] : '' ) . '">',
497 648 );
498 649 }
499 650 }
500 651 else
@@ -507,8 +658,9 @@
507 658 {
508 659 $images[] = array(
509 660 'title' => esc_attr( get_the_title( $gallery_attachment ) ),
510 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.
511 663 'image' => wp_get_attachment_image( $gallery_attachment, apply_filters( 'single_property_small_thumbnail_size', 'thumbnail' ) ),
512 664 'attachment_id' => $gallery_attachment,
513 665 );
514 666 }
@@ -542,9 +694,14 @@
542 694 * @subpackage Property
543 695 * @return void
544 696 */
545 697 function propertyhive_template_single_floor_area() {
546 - ph_get_template( 'single-property/floor-area.php' );
698 + global $property;
699 +
700 + if ( $property->department == 'commercial' || ph_get_custom_department_based_on( $property->department ) == 'commercial' )
701 + {
702 + ph_get_template( 'single-property/floor-area.php' );
703 + }
547 704 }
548 705 }
549 706
550 707 if ( ! function_exists( 'propertyhive_template_single_price' ) ) {
@@ -579,9 +736,23 @@
579 736 $fees = nl2br(get_option('propertyhive_lettings_fees_commercial', ''));
580 737 }
581 738 }
582 739
583 - ph_get_template( 'single-property/price.php', array( 'fees' => $fees ) );
740 + $price_qualifier = '';
741 + if (
742 + (
743 + $property->department == 'residential-sales' ||
744 + ph_get_custom_department_based_on($property->department) == 'residential-sales' ||
745 + $property->department == 'commercial' ||
746 + ph_get_custom_department_based_on($property->department) == 'commercial'
747 + ) &&
748 + $property->price_qualifier != ''
749 + )
750 + {
751 + $price_qualifier = $property->price_qualifier;
752 + }
753 +
754 + ph_get_template( 'single-property/price.php', array( 'price_qualifier' => $price_qualifier, 'fees' => $fees ) );
584 755 }
585 756 }
586 757
587 758 if ( ! function_exists( 'propertyhive_template_single_meta' ) ) {
@@ -622,9 +793,9 @@
622 793 'value' => $property->availability
623 794 );
624 795 }
625 796
626 - if ( $property->department != 'commercial' )
797 + if ( $property->department != 'commercial' && ph_get_custom_department_based_on( $property->department ) != 'commercial' )
627 798 {
628 799 if ( $property->bedrooms > 0 )
629 800 {
630 801 $meta['bedrooms'] = array(
@@ -663,11 +834,19 @@
663 834 'label' => __('Outside Space', 'propertyhive'),
664 835 'value' => $property->outside_space
665 836 );
666 837 }
838 +
839 + if ( $property->council_tax_band != '' )
840 + {
841 + $meta['council-tax-band'] = array(
842 + 'label' => __('Council Tax Band', 'propertyhive'),
843 + 'value' => $property->council_tax_band
844 + );
845 + }
667 846 }
668 847
669 - if ( $property->department == 'residential-sales' )
848 + if ( $property->department == 'residential-sales' || ph_get_custom_department_based_on( $property->department ) == 'residential-sales' )
670 849 {
671 850 if ( $property->tenure != '' )
672 851 {
673 852 $meta['tenure'] = array(
@@ -673,12 +852,63 @@
673 852 $meta['tenure'] = array(
674 853 'label' => __('Tenure', 'propertyhive'),
675 854 'value' => $property->tenure
676 855 );
856 +
857 + if ( in_array( strtolower($property->tenure), apply_filters('propertyhive_leasehold_tenure_names', array( 'leasehold', 'share of freehold' ) ) ) )
858 + {
859 + if ( $property->leasehold_years_remaining != '' )
860 + {
861 + $meta['leasehold-years-remaining'] = array(
862 + 'label' => __('Leasehold Remaining', 'propertyhive'),
863 + 'value' => $property->leasehold_years_remaining . ' ' . __( 'years', 'propertyhive' )
864 + );
865 + }
866 +
867 + if ( $property->ground_rent != '' )
868 + {
869 + $meta['ground-rent'] = array(
870 + 'label' => __('Ground Rent', 'propertyhive'),
871 + 'value' => '&pound;' . ph_display_price_field($property->ground_rent, true)
872 + );
873 + }
874 +
875 + if ( $property->ground_rent_review_years != '' )
876 + {
877 + $meta['ground-rent-review-years'] = array(
878 + 'label' => __('Ground Rent Review Period', 'propertyhive'),
879 + 'value' => $property->ground_rent_review_years . ' ' . __( 'years', 'propertyhive' )
880 + );
881 + }
882 +
883 + if ( $property->service_charge != '' )
884 + {
885 + $meta['service-charge'] = array(
886 + 'label' => __('Service Charge', 'propertyhive'),
887 + 'value' => '&pound;' . ph_display_price_field($property->service_charge, true)
888 + );
889 + }
890 +
891 + if ( $property->service_charge_review_years != '' )
892 + {
893 + $meta['service-charge-review-years'] = array(
894 + 'label' => __('Service Charge Review Period', 'propertyhive'),
895 + 'value' => $property->service_charge_review_years . ' ' . __( 'years', 'propertyhive' )
896 + );
897 + }
898 +
899 + if ( $property->shared_ownership == 'yes' )
900 + {
901 + $meta['shared-ownership'] = array(
902 + 'label' => __('Shared Ownership', 'propertyhive'),
903 + 'value' => $property->shared_ownership . ( $property->shared_ownership_percentage != '' ? ' (' . $property->shared_ownership_percentage . '%)' : '' )
904 + );
905 + }
906 + }
677 907 }
678 908 }
679 909
680 - if ( $property->department == 'residential-lettings' )
910 + if ( $property->department == 'residential-lettings' || ph_get_custom_department_based_on( $property->department ) == 'residential-lettings' )
681 911 {
682 912 if ( $property->furnished != '' )
683 913 {
684 914 $meta['furnished'] = array(
@@ -723,18 +953,11 @@
723 953 ph_get_template( 'single-property/share.php' );
724 954 }
725 955 }
726 956
727 -if ( ! function_exists( 'propertyhive_template_single_actions' ) ) {
957 +if ( ! function_exists( 'propertyhive_get_template_single_floorplans_action' ) ) {
728 958
729 - /**
730 - * Output the product actions (make enquiry etc)
731 - *
732 - * @access public
733 - * @subpackage Property
734 - * @return void
735 - */
736 - function propertyhive_template_single_actions() {
959 + function propertyhive_get_template_single_floorplans_action() {
737 960
738 961 global $post, $property;
739 962
740 963 $actions = array();
@@ -763,9 +986,9 @@
763 986 if ( !empty( $floorplan_ids ) )
764 987 {
765 988 foreach ($floorplan_ids as $floorplan_id)
766 989 {
767 - $label = 'Floorplan';
990 + $label = __( 'Floorplan', 'propertyhive' );
768 991
769 992 $attachment_data = wp_prepare_attachment_for_js( $floorplan_id );
770 993 if ( isset( $attachment_data['caption'] ) && $attachment_data['caption'] != '' )
771 994 {
@@ -774,9 +997,9 @@
774 997
775 998
776 999 $actions[] = array(
777 1000 'href' => wp_get_attachment_url( $floorplan_id ),
778 - 'label' => __( $label, 'propertyhive' ),
1001 + 'label' => $label,
779 1002 'class' => 'action-floorplans',
780 1003 'attributes' => array(
781 1004 'data-fancybox' => 'floorplans'
782 1005 )
@@ -784,8 +1007,23 @@
784 1007 }
785 1008 }
786 1009 }
787 1010
1011 + $actions = apply_filters( 'propertyhive_single_property_floorplans_actions', $actions );
1012 +
1013 + return $actions;
1014 + }
1015 +
1016 +}
1017 +
1018 +if ( ! function_exists( 'propertyhive_get_template_single_brochures_action' ) ) {
1019 +
1020 + function propertyhive_get_template_single_brochures_action() {
1021 +
1022 + global $post, $property;
1023 +
1024 + $actions = array();
1025 +
788 1026 if ( get_option('propertyhive_brochures_stored_as', '') == 'urls' )
789 1027 {
790 1028 $brochure_urls = $property->_brochure_urls;
791 1029 if ( is_array($brochure_urls) && !empty( $brochure_urls ) )
@@ -821,8 +1059,23 @@
821 1059 }
822 1060 }
823 1061 }
824 1062
1063 + $actions = apply_filters( 'propertyhive_single_property_brochures_actions', $actions );
1064 +
1065 + return $actions;
1066 + }
1067 +
1068 +}
1069 +
1070 +if ( ! function_exists( 'propertyhive_get_template_single_epcs_action' ) ) {
1071 +
1072 + function propertyhive_get_template_single_epcs_action() {
1073 +
1074 + global $post, $property;
1075 +
1076 + $actions = array();
1077 +
825 1078 if ( get_option('propertyhive_epcs_stored_as', '') == 'urls' )
826 1079 {
827 1080 $epc_urls = $property->_epc_urls;
828 1081 if ( is_array($epc_urls) && !empty( $epc_urls ) )
@@ -861,8 +1114,23 @@
861 1114 }
862 1115 }
863 1116 }
864 1117
1118 + $actions = apply_filters( 'propertyhive_single_property_epcs_actions', $actions );
1119 +
1120 + return $actions;
1121 + }
1122 +
1123 +}
1124 +
1125 +if ( ! function_exists( 'propertyhive_get_template_single_virtual_tours_action' ) ) {
1126 +
1127 + function propertyhive_get_template_single_virtual_tours_action() {
1128 +
1129 + global $post, $property;
1130 +
1131 + $actions = array();
1132 +
865 1133 $virtual_tours = $property->get_virtual_tours();
866 1134 if ( !empty( $virtual_tours ) )
867 1135 {
868 1136 foreach ($virtual_tours as $virtual_tour)
@@ -874,9 +1142,9 @@
874 1142 }
875 1143
876 1144 $actions[] = array(
877 1145 'href' => $virtual_tour['url'],
878 - 'label' => __( $virtual_tour['label'], 'propertyhive' ),
1146 + 'label' => $virtual_tour['label'],
879 1147 'class' => 'action-virtual-tour',
880 1148 'attributes' => $attributes,
881 1149 );
882 1150 }
@@ -881,21 +1149,90 @@
881 1149 );
882 1150 }
883 1151 }
884 1152
885 - /*$actions[] = array(
886 - 'href' => '',
887 - 'label' => __( 'View on Map', 'propertyhive' ),
888 - 'class' => 'action-map'
1153 + $actions = apply_filters( 'propertyhive_single_property_virtual_tours_actions', $actions );
1154 +
1155 + return $actions;
1156 + }
1157 +
1158 +}
1159 +
1160 +if ( ! function_exists( 'propertyhive_get_template_single_material_information_action' ) ) {
1161 +
1162 + function propertyhive_get_template_single_material_information_action() {
1163 +
1164 + global $post, $property;
1165 +
1166 + $actions = array();
1167 +
1168 + $material_information = $property->get_material_information();
1169 +
1170 + // Only show if at least one bit of material information has been completed
1171 + if ( empty($material_information) )
1172 + {
1173 + return $actions;
1174 + }
1175 +
1176 + $actions[] = array(
1177 + 'href' => 'javascript:;',
1178 + 'label' => __( 'Utilities & More', 'propertyhive' ),
1179 + 'class' => 'action-material-information',
1180 + 'attributes' => array(
1181 + 'data-fancybox' => '',
1182 + 'data-src' => '#ph_material_information'
1183 + ),
889 1184 );
890 1185
891 - $actions[] = array(
892 - 'href' => '',
893 - 'label' => __( 'Street View', 'propertyhive' ),
894 - 'class' => 'action-street-view'
895 - );*/
1186 + echo '<div id="ph_material_information" style="display:none;">';
896 1187
1188 + ph_get_template( 'single-property/material-information.php' );
1189 +
1190 + echo '</div>';
1191 +
1192 + $actions = apply_filters( 'propertyhive_single_property_material_information_actions', $actions );
1193 +
1194 + return $actions;
1195 + }
1196 +
1197 +}
1198 +
1199 +if ( ! function_exists( 'propertyhive_get_template_single_actions' ) ) {
1200 +
1201 + function propertyhive_get_template_single_actions() {
1202 +
1203 + global $post, $property;
1204 +
1205 + $actions = array_merge(
1206 + propertyhive_get_template_single_floorplans_action(),
1207 + propertyhive_get_template_single_brochures_action(),
1208 + propertyhive_get_template_single_epcs_action(),
1209 + propertyhive_get_template_single_virtual_tours_action(),
1210 + propertyhive_get_template_single_material_information_action(),
1211 + );
1212 +
1213 +
897 1214 $actions = apply_filters( 'propertyhive_single_property_actions', $actions );
1215 +
1216 + return $actions;
1217 + }
1218 +
1219 +}
1220 +
1221 +if ( ! function_exists( 'propertyhive_template_single_actions' ) ) {
1222 +
1223 + /**
1224 + * Output the product actions (make enquiry etc)
1225 + *
1226 + * @access public
1227 + * @subpackage Property
1228 + * @return void
1229 + */
1230 + function propertyhive_template_single_actions() {
1231 +
1232 + global $post, $property;
1233 +
1234 + $actions = propertyhive_get_template_single_actions();
898 1235
899 1236 ph_get_template( 'single-property/actions.php', array( 'actions' => $actions ) );
900 1237 }
901 1238 }
@@ -984,8 +1321,10 @@
984 1321 if ( in_array('applicant', $contact_types) )
985 1322 {
986 1323 $applicant_profiles = $contact->applicant_profiles;
987 1324
1325 + $buy_rent = array();
1326 +
988 1327 if ( $applicant_profiles && $applicant_profiles > 0 )
989 1328 {
990 1329 for ( $i = 0; $i < $applicant_profiles; ++$i )
991 1330 {
@@ -992,12 +1331,23 @@
992 1331 $pages['requirements'] = array(
993 1332 'name' => __( 'Requirements', 'propertyhive' ),
994 1333 );
995 1334
996 - break; // At the moment we'll only allow them to manage the first set of requirements
1335 + $applicant_profile = $contact->{'applicant_profile_' . $i};
1336 +
1337 + if ( isset($applicant_profile['department']) && $applicant_profile['department'] == 'residential-sales' )
1338 + {
1339 + $buy_rent[] = 'To Buy';
1340 + }
1341 + if ( isset($applicant_profile['department']) && $applicant_profile['department'] == 'residential-lettings' )
1342 + {
1343 + $buy_rent[] = 'To Rent';
1344 + }
997 1345 }
998 1346 }
999 1347
1348 + $buy_rent = array_unique($buy_rent);
1349 +
1000 1350 // Check viewing module is active and that viewings exist, either future or past
1001 1351 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
1002 1352 {
1003 1353 $args = array(
@@ -1004,8 +1354,9 @@
1004 1354 'post_type' => 'viewing',
1005 1355 'posts_per_page' => 1,
1006 1356 'post_status' => 'publish',
1007 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.
1008 1359 'meta_query' => array(
1009 1360 array(
1010 1361 'key' => '_applicant_contact_id',
1011 1362 'value' => $contact->id
@@ -1016,9 +1367,9 @@
1016 1367
1017 1368 if ( $viewings_query->have_posts() )
1018 1369 {
1019 1370 $pages['applicant_viewings'] = array(
1020 - 'name' => __( 'Viewings', 'propertyhive' ),
1371 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($buy_rent) ? ' ' . implode('/', $buy_rent) : '' ),
1021 1372 );
1022 1373 }
1023 1374 wp_reset_postdata();
1024 1375 }
@@ -1024,8 +1375,10 @@
1024 1375 }
1025 1376 }
1026 1377 if ( in_array('owner', $contact_types) )
1027 1378 {
1379 + $sell_let = array();
1380 +
1028 1381 // Get properties belonging to this owner
1029 1382 $args = array(
1030 1383 'post_type' => 'property',
1031 1384 'nopaging' => true,
@@ -1030,8 +1383,9 @@
1030 1383 'post_type' => 'property',
1031 1384 'nopaging' => true,
1032 1385 'post_status' => 'publish',
1033 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.
1034 1388 'meta_query' => array(
1035 1389 'relation' => 'OR',
1036 1390 array(
1037 1391 'key' => '_owner_contact_id',
@@ -1055,9 +1409,18 @@
1055 1409 while ( $properties_query->have_posts() )
1056 1410 {
1057 1411 $properties_query->the_post();
1058 1412
1059 - $property_ids[] = get_the_id();
1413 + $property_ids[] = get_the_ID();
1414 +
1415 + if ( get_post_meta( get_the_ID(), '_department', TRUE ) == 'residential-sales' )
1416 + {
1417 + $sell_let[] = 'To Sell';
1418 + }
1419 + if ( get_post_meta( get_the_ID(), '_department', TRUE ) == 'residential-lettings' )
1420 + {
1421 + $sell_let[] = 'To Let';
1422 + }
1060 1423 }
1061 1424
1062 1425 $pages['owner_properties'] = array(
1063 1426 'name' => __( 'Properties', 'propertyhive' ),
@@ -1064,8 +1427,10 @@
1064 1427 );
1065 1428 }
1066 1429 wp_reset_postdata();
1067 1430
1431 + $sell_let = array_unique($sell_let);
1432 +
1068 1433 // Check viewing module is active and that viewings exist, either future or past
1069 1434 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
1070 1435 {
1071 1436 $past_viewings = array();
@@ -1077,8 +1442,9 @@
1077 1442 'post_type' => 'viewing',
1078 1443 'posts_per_page' => 1,
1079 1444 'post_status' => 'publish',
1080 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.
1081 1447 'meta_query' => array(
1082 1448 array(
1083 1449 'key' => '_property_id',
1084 1450 'value' => $property_ids,
@@ -1090,9 +1456,9 @@
1090 1456
1091 1457 if ( $viewings_query->have_posts() )
1092 1458 {
1093 1459 $pages['owner_viewings'] = array(
1094 - 'name' => __( 'Viewings', 'propertyhive' ),
1460 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($sell_let) ? ' ' . implode('/', $sell_let) : '' ),
1095 1461 );
1096 1462 }
1097 1463 wp_reset_postdata();
1098 1464 }
@@ -1107,9 +1473,9 @@
1107 1473 );*/
1108 1474
1109 1475 $pages['logout'] = array(
1110 1476 'name' => __( 'Logout', 'propertyhive' ),
1111 - 'href' => home_url() . '?logout=1' // Logout URL
1477 + 'href' => wp_nonce_url( add_query_arg( 'logout', '1', home_url( '/' ) ), 'log-out' ) // Logout URL
1112 1478 );
1113 1479
1114 1480 return $pages;
1115 1481 }
@@ -1198,19 +1564,36 @@
1198 1564 * @return void
1199 1565 */
1200 1566 function propertyhive_my_account_requirements() {
1201 1567
1202 - $form_controls = ph_get_applicant_requirements_form_fields();
1203 -
1204 - $form_controls = apply_filters( 'propertyhive_applicant_requirements_form_fields', $form_controls );
1568 + $user_id = get_current_user_id();
1205 1569
1206 - // Remove office as this is only required on initial sign up (at the moment anyway)
1207 - if ( isset($form_controls['office_id']) )
1570 + $contact = new PH_Contact( '', $user_id );
1571 +
1572 + if ( is_array($contact->contact_types) && in_array('applicant', $contact->contact_types) )
1208 1573 {
1209 - unset($form_controls['office_id']);
1574 + $applicant_profiles = $contact->applicant_profiles;
1575 +
1576 + for ( $i = 0; $i < $applicant_profiles; ++$i )
1577 + {
1578 + $form_controls = ph_get_applicant_requirements_form_fields($contact->{'applicant_profile_' . $i});
1579 +
1580 + $form_controls = apply_filters( 'propertyhive_applicant_requirements_form_fields', $form_controls, $contact->{'applicant_profile_' . $i} );
1581 +
1582 + $form_controls['profile_id'] = array(
1583 + 'type' => 'hidden',
1584 + 'value' => $i,
1585 + );
1586 +
1587 + // Remove office as this is only required on initial sign up (at the moment anyway)
1588 + if ( isset($form_controls['office_id']) )
1589 + {
1590 + unset($form_controls['office_id']);
1591 + }
1592 +
1593 + ph_get_template( 'account/requirements.php', array( 'form_controls' => $form_controls ) );
1594 + }
1210 1595 }
1211 -
1212 - ph_get_template( 'account/requirements.php', array( 'form_controls' => $form_controls ) );
1213 1596 }
1214 1597 }
1215 1598
1216 1599 if ( ! function_exists( 'propertyhive_my_account_applicant_viewings' ) ) {
@@ -1237,9 +1620,11 @@
1237 1620 'fields' => 'ids',
1238 1621 'orderby' => 'meta_value',
1239 1622 'order' => 'DESC',
1240 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.
1241 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.
1242 1627 'meta_query' => array(
1243 1628 array(
1244 1629 'key' => '_applicant_contact_id',
1245 1630 'value' => $contact->id
@@ -1250,9 +1635,9 @@
1250 1635 // Do past viewings
1251 1636 $args2 = $args;
1252 1637 $args2['meta_query'][] = array(
1253 1638 'key' => '_start_date_time',
1254 - 'value' => date("Y-m-d H:i:s"),
1639 + 'value' => gmdate("Y-m-d H:i:s"),
1255 1640 'compare' => '<='
1256 1641 );
1257 1642
1258 1643 $viewings_query = new WP_Query( $args2 );
@@ -1273,9 +1658,9 @@
1273 1658 // Do upcoming viewings
1274 1659 $args2 = $args;
1275 1660 $args2['meta_query'][] = array(
1276 1661 'key' => '_start_date_time',
1277 - 'value' => date("Y-m-d H:i:s"),
1662 + 'value' => gmdate("Y-m-d H:i:s"),
1278 1663 'compare' => '>='
1279 1664 );
1280 1665
1281 1666 $viewings_query = new WP_Query( $args2 );
@@ -1316,8 +1701,9 @@
1316 1701 'post_type' => 'property',
1317 1702 'nopaging' => true,
1318 1703 'post_status' => 'publish',
1319 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.
1320 1706 'meta_query' => array(
1321 1707 'relation' => 'OR',
1322 1708 array(
1323 1709 'key' => '_owner_contact_id',
@@ -1369,8 +1755,9 @@
1369 1755 'post_type' => 'property',
1370 1756 'nopaging' => true,
1371 1757 'post_status' => 'publish',
1372 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.
1373 1760 'meta_query' => array(
1374 1761 'relation' => 'OR',
1375 1762 array(
1376 1763 'key' => '_owner_contact_id',
@@ -1412,9 +1799,11 @@
1412 1799 'fields' => 'ids',
1413 1800 'orderby' => 'meta_value',
1414 1801 'order' => 'DESC',
1415 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.
1416 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.
1417 1806 'meta_query' => array(
1418 1807 array(
1419 1808 'key' => '_property_id',
1420 1809 'value' => $property_ids,
@@ -1426,9 +1815,9 @@
1426 1815 // Do past viewings
1427 1816 $args2 = $args;
1428 1817 $args2['meta_query'][] = array(
1429 1818 'key' => '_start_date_time',
1430 - 'value' => date("Y-m-d H:i:s"),
1819 + 'value' => gmdate("Y-m-d H:i:s"),
1431 1820 'compare' => '<='
1432 1821 );
1433 1822
1434 1823 $viewings_query = new WP_Query( $args2 );
@@ -1449,9 +1838,9 @@
1449 1838 // Do upcoming viewings
1450 1839 $args2 = $args;
1451 1840 $args2['meta_query'][] = array(
1452 1841 'key' => '_start_date_time',
1453 - 'value' => date("Y-m-d H:i:s"),
1842 + 'value' => gmdate("Y-m-d H:i:s"),
1454 1843 'compare' => '>='
1455 1844 );
1456 1845
1457 1846 $viewings_query = new WP_Query( $args2 );
@@ -1485,5 +1874,292 @@
1485 1874 function propertyhive_my_account_delete() {
1486 1875
1487 1876 ph_get_template( 'account/delete.php' );
1488 1877 }
1489 -}
1878 +}
1879 +
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.
1882 +function template_assistant_loop_search_results_per_page( $cols )
1883 +{
1884 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1885 +
1886 + if ( isset($current_settings['search_result_columns']) && in_array($current_settings['search_result_columns'], array(3,4)) )
1887 + {
1888 + return 12;
1889 + }
1890 +
1891 + return $cols;
1892 +}
1893 +
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.
1896 +function template_assistant_search_result_columns( $cols = 1 )
1897 +{
1898 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1899 +
1900 + if ( isset($current_settings['search_result_columns']) && in_array($current_settings['search_result_columns'], array(1,2,3,4)) )
1901 + {
1902 + return $current_settings['search_result_columns'];
1903 + }
1904 +
1905 + return 1;
1906 +}
1907 +
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.
1910 +function template_assistant_property_columns_post_class( $classes, $class = '', $post_id = '' )
1911 +{
1912 + if ( ! $post_id || get_post_type( $post_id ) !== 'property' )
1913 + return $classes;
1914 +
1915 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1916 +
1917 + if ( isset($current_settings['search_result_columns']) && in_array($current_settings['search_result_columns'], array(2,3,4)) )
1918 + {
1919 + $property = get_property( $post_id );
1920 +
1921 + if ( $property )
1922 + {
1923 + $classes[] = 'ph-cols-' . $current_settings['search_result_columns'];
1924 +
1925 + if ( ($key = array_search('clear', $classes)) !== false )
1926 + {
1927 + unset($classes[$key]);
1928 + }
1929 + }
1930 + }
1931 +
1932 + return $classes;
1933 +}
1934 +
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.
1937 +function load_template_assistant_styles()
1938 +{
1939 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1940 +
1941 + if (
1942 + is_post_type_archive('property')
1943 + ||
1944 + ( isset($current_settings['search_result_css_all_pages']) && $current_settings['search_result_css_all_pages'] == 'yes' )
1945 + )
1946 + {
1947 + if ( isset( $current_settings['search_result_css'] ) && is_string( $current_settings['search_result_css'] ) )
1948 + {
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>';
1955 + }
1956 + }
1957 +}
1958 +
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.
1961 +function template_assistant_change_default_order( $orderby )
1962 +{
1963 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1964 +
1965 + if ( isset($current_settings['search_result_default_order']) && $current_settings['search_result_default_order'] != '' )
1966 + {
1967 + return $current_settings['search_result_default_order'];
1968 + }
1969 +
1970 + return $orderby;
1971 +}
1972 +
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.
1975 +function template_assistant_search_result_image_size_changes( $image_size )
1976 +{
1977 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1978 +
1979 + if ( isset($current_settings['search_result_image_size']) && $current_settings['search_result_image_size'] != '' )
1980 + {
1981 + $image_size = $current_settings['search_result_image_size'];
1982 + }
1983 +
1984 + return $image_size;
1985 +}
1986 +
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.
1989 +function template_assistant_search_result_field_changes()
1990 +{
1991 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1992 +
1993 + if ( isset($current_settings['search_result_fields']) && is_array($current_settings['search_result_fields']) && !empty($current_settings['search_result_fields']) )
1994 + {
1995 + remove_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_floor_area', 5 );
1996 + remove_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_price', 10 );
1997 + remove_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_summary', 20 );
1998 + remove_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_actions', 30 );
1999 +
2000 + $priority = 5;
2001 + foreach ( $current_settings['search_result_fields'] as $search_result_field )
2002 + {
2003 + if ( substr($search_result_field, 0, 12) == 'custom_field' )
2004 + {
2005 + // custom field output here
2006 + $custom_field = substr($search_result_field, 12);
2007 +
2008 + add_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_custom_field', $priority );
2009 +
2010 + $priority += 5;
2011 + continue;
2012 + }
2013 +
2014 + switch ( $search_result_field )
2015 + {
2016 + case "price":
2017 + case "floor_area":
2018 + case "summary":
2019 + case "actions":
2020 + {
2021 + add_action( 'propertyhive_after_search_results_loop_item_title', 'propertyhive_template_loop_' . $search_result_field, $priority );
2022 + break;
2023 + }
2024 + case "availability":
2025 + {
2026 + add_action( 'propertyhive_after_search_results_loop_item_title', function() { global $property; echo '<div class="availability">' . esc_html( $property->availability ) . '</div>'; }, $priority );
2027 + break;
2028 + }
2029 + case "property_type":
2030 + {
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 );
2032 + break;
2033 + }
2034 + case "available_date":
2035 + {
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 );
2037 + break;
2038 + }
2039 + case "rooms":
2040 + {
2041 + add_action( 'propertyhive_after_search_results_loop_item_title', function() {
2042 + global $property;
2043 +
2044 + if ( ($property->bedrooms != '' && $property->bedrooms != '0') || ($property->bathrooms != '' && $property->bathrooms != '0') || ($property->reception_rooms != '' && $property->reception_rooms != '0') )
2045 + {
2046 + echo '<div class="rooms">';
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>'; }
2050 + echo '</div>';
2051 + }
2052 + }, $priority );
2053 + break;
2054 + }
2055 + default:
2056 + {
2057 + echo 'unknown search result field requested';
2058 + }
2059 + }
2060 +
2061 + $priority += 5;
2062 + }
2063 + }
2064 +}
2065 +
2066 +function propertyhive_template_loop_custom_field()
2067 +{
2068 + global $property;
2069 +
2070 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
2071 +
2072 + foreach ( $current_settings['search_result_fields'] as $search_result_field )
2073 + {
2074 + if ( substr($search_result_field, 0, 12) == 'custom_field' )
2075 + {
2076 + // custom field output here
2077 + $custom_field = substr($search_result_field, 12);
2078 +
2079 + $value = $property->{$custom_field};
2080 + $value = is_array($value) ? implode(", ", $value) : $value;
2081 +
2082 + if ( $value != '' )
2083 + {
2084 + echo '<div class="custom-field custom-field-' . esc_attr( sanitize_title(trim($custom_field, "_")) ) . '">' . wp_kses_post( $value ) . '</div>';
2085 + }
2086 + }
2087 + }
2088 +}
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 +
2112 +add_action( 'propertyhive_before_search_results_loop_item_title', 'propertyhive_add_flag' );
2113 +function propertyhive_add_flag()
2114 +{
2115 + global $property;
2116 +
2117 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
2118 +
2119 + if ( isset($current_settings['flags_active']) && $current_settings['flags_active'] == '1' )
2120 + {
2121 + $flag = propertyhive_get_flag();
2122 +
2123 + if ( $flag != '' )
2124 + {
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>';
2126 + }
2127 + }
2128 +}
2129 +
2130 +add_action( 'propertyhive_before_single_property_images', 'propertyhive_add_flag_single', 5 );
2131 +function propertyhive_add_flag_single()
2132 +{
2133 + global $property;
2134 +
2135 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
2136 +
2137 + if ( isset($current_settings['flags_active_single']) && $current_settings['flags_active_single'] == '1' )
2138 + {
2139 + $flag = propertyhive_get_flag();
2140 +
2141 + if ( $flag != '' )
2142 + {
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>';
2144 + }
2145 + }
2146 +}
2147 +
2148 +function propertyhive_get_flag()
2149 +{
2150 + global $property;
2151 +
2152 + $flag = $property->availability;
2153 +
2154 + if ( $property->marketing_flag != '' )
2155 + {
2156 + $flag = $property->marketing_flag;
2157 + }
2158 +
2159 + $flag = apply_filters( 'propertyhive_template_assistant_flag', $flag );
2160 +
2161 + return $flag;
2162 +}
2163 +
2164 +
2165 +