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 +756 -76 1.4.532.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,37 +1114,125 @@
861 1114 }
862 1115 }
863 1116 }
864 1117
865 - $virtual_tour_urls = $property->get_virtual_tour_urls();
866 - if ( !empty( $virtual_tour_urls ) )
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 +
1133 + $virtual_tours = $property->get_virtual_tours();
1134 + if ( !empty( $virtual_tours ) )
867 1135 {
868 - foreach ($virtual_tour_urls as $virtual_tour_url)
1136 + foreach ($virtual_tours as $virtual_tour)
869 1137 {
1138 + $attributes = array('target' => '_blank');
1139 + if ( strpos($virtual_tour['url'], 'yout') !== FALSE || strpos($virtual_tour['url'], 'vimeo') !== FALSE )
1140 + {
1141 + $attributes['data-fancybox'] = '';
1142 + }
1143 +
870 1144 $actions[] = array(
871 - 'href' => $virtual_tour_url,
872 - 'label' => __( 'Virtual Tour', 'propertyhive' ),
1145 + 'href' => $virtual_tour['url'],
1146 + 'label' => $virtual_tour['label'],
873 1147 'class' => 'action-virtual-tour',
874 - 'attributes' => array(
875 - 'target' => '_blank'
876 - )
1148 + 'attributes' => $attributes,
877 1149 );
878 1150 }
879 1151 }
880 1152
881 - /*$actions[] = array(
882 - 'href' => '',
883 - 'label' => __( 'View on Map', 'propertyhive' ),
884 - '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 + ),
885 1184 );
886 1185
887 - $actions[] = array(
888 - 'href' => '',
889 - 'label' => __( 'Street View', 'propertyhive' ),
890 - 'class' => 'action-street-view'
891 - );*/
1186 + echo '<div id="ph_material_information" style="display:none;">';
892 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 +
893 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();
894 1235
895 1236 ph_get_template( 'single-property/actions.php', array( 'actions' => $actions ) );
896 1237 }
897 1238 }
@@ -980,8 +1321,10 @@
980 1321 if ( in_array('applicant', $contact_types) )
981 1322 {
982 1323 $applicant_profiles = $contact->applicant_profiles;
983 1324
1325 + $buy_rent = array();
1326 +
984 1327 if ( $applicant_profiles && $applicant_profiles > 0 )
985 1328 {
986 1329 for ( $i = 0; $i < $applicant_profiles; ++$i )
987 1330 {
@@ -988,12 +1331,23 @@
988 1331 $pages['requirements'] = array(
989 1332 'name' => __( 'Requirements', 'propertyhive' ),
990 1333 );
991 1334
992 - 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 + }
993 1345 }
994 1346 }
995 1347
1348 + $buy_rent = array_unique($buy_rent);
1349 +
996 1350 // Check viewing module is active and that viewings exist, either future or past
997 1351 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
998 1352 {
999 1353 $args = array(
@@ -1000,8 +1354,9 @@
1000 1354 'post_type' => 'viewing',
1001 1355 'posts_per_page' => 1,
1002 1356 'post_status' => 'publish',
1003 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.
1004 1359 'meta_query' => array(
1005 1360 array(
1006 1361 'key' => '_applicant_contact_id',
1007 1362 'value' => $contact->id
@@ -1012,9 +1367,9 @@
1012 1367
1013 1368 if ( $viewings_query->have_posts() )
1014 1369 {
1015 1370 $pages['applicant_viewings'] = array(
1016 - 'name' => __( 'Viewings', 'propertyhive' ),
1371 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($buy_rent) ? ' ' . implode('/', $buy_rent) : '' ),
1017 1372 );
1018 1373 }
1019 1374 wp_reset_postdata();
1020 1375 }
@@ -1020,8 +1375,10 @@
1020 1375 }
1021 1376 }
1022 1377 if ( in_array('owner', $contact_types) )
1023 1378 {
1379 + $sell_let = array();
1380 +
1024 1381 // Get properties belonging to this owner
1025 1382 $args = array(
1026 1383 'post_type' => 'property',
1027 1384 'nopaging' => true,
@@ -1026,8 +1383,9 @@
1026 1383 'post_type' => 'property',
1027 1384 'nopaging' => true,
1028 1385 'post_status' => 'publish',
1029 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.
1030 1388 'meta_query' => array(
1031 1389 'relation' => 'OR',
1032 1390 array(
1033 1391 'key' => '_owner_contact_id',
@@ -1051,9 +1409,18 @@
1051 1409 while ( $properties_query->have_posts() )
1052 1410 {
1053 1411 $properties_query->the_post();
1054 1412
1055 - $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 + }
1056 1423 }
1057 1424
1058 1425 $pages['owner_properties'] = array(
1059 1426 'name' => __( 'Properties', 'propertyhive' ),
@@ -1060,8 +1427,10 @@
1060 1427 );
1061 1428 }
1062 1429 wp_reset_postdata();
1063 1430
1431 + $sell_let = array_unique($sell_let);
1432 +
1064 1433 // Check viewing module is active and that viewings exist, either future or past
1065 1434 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
1066 1435 {
1067 1436 $past_viewings = array();
@@ -1073,8 +1442,9 @@
1073 1442 'post_type' => 'viewing',
1074 1443 'posts_per_page' => 1,
1075 1444 'post_status' => 'publish',
1076 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.
1077 1447 'meta_query' => array(
1078 1448 array(
1079 1449 'key' => '_property_id',
1080 1450 'value' => $property_ids,
@@ -1086,9 +1456,9 @@
1086 1456
1087 1457 if ( $viewings_query->have_posts() )
1088 1458 {
1089 1459 $pages['owner_viewings'] = array(
1090 - 'name' => __( 'Viewings', 'propertyhive' ),
1460 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($sell_let) ? ' ' . implode('/', $sell_let) : '' ),
1091 1461 );
1092 1462 }
1093 1463 wp_reset_postdata();
1094 1464 }
@@ -1103,9 +1473,9 @@
1103 1473 );*/
1104 1474
1105 1475 $pages['logout'] = array(
1106 1476 'name' => __( 'Logout', 'propertyhive' ),
1107 - 'href' => home_url() . '?logout=1' // Logout URL
1477 + 'href' => wp_nonce_url( add_query_arg( 'logout', '1', home_url( '/' ) ), 'log-out' ) // Logout URL
1108 1478 );
1109 1479
1110 1480 return $pages;
1111 1481 }
@@ -1194,19 +1564,36 @@
1194 1564 * @return void
1195 1565 */
1196 1566 function propertyhive_my_account_requirements() {
1197 1567
1198 - $form_controls = ph_get_applicant_requirements_form_fields();
1199 -
1200 - $form_controls = apply_filters( 'propertyhive_applicant_requirements_form_fields', $form_controls );
1568 + $user_id = get_current_user_id();
1201 1569
1202 - // Remove office as this is only required on initial sign up (at the moment anyway)
1203 - 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) )
1204 1573 {
1205 - 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 + }
1206 1595 }
1207 -
1208 - ph_get_template( 'account/requirements.php', array( 'form_controls' => $form_controls ) );
1209 1596 }
1210 1597 }
1211 1598
1212 1599 if ( ! function_exists( 'propertyhive_my_account_applicant_viewings' ) ) {
@@ -1233,9 +1620,11 @@
1233 1620 'fields' => 'ids',
1234 1621 'orderby' => 'meta_value',
1235 1622 'order' => 'DESC',
1236 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.
1237 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.
1238 1627 'meta_query' => array(
1239 1628 array(
1240 1629 'key' => '_applicant_contact_id',
1241 1630 'value' => $contact->id
@@ -1246,9 +1635,9 @@
1246 1635 // Do past viewings
1247 1636 $args2 = $args;
1248 1637 $args2['meta_query'][] = array(
1249 1638 'key' => '_start_date_time',
1250 - 'value' => date("Y-m-d H:i:s"),
1639 + 'value' => gmdate("Y-m-d H:i:s"),
1251 1640 'compare' => '<='
1252 1641 );
1253 1642
1254 1643 $viewings_query = new WP_Query( $args2 );
@@ -1269,9 +1658,9 @@
1269 1658 // Do upcoming viewings
1270 1659 $args2 = $args;
1271 1660 $args2['meta_query'][] = array(
1272 1661 'key' => '_start_date_time',
1273 - 'value' => date("Y-m-d H:i:s"),
1662 + 'value' => gmdate("Y-m-d H:i:s"),
1274 1663 'compare' => '>='
1275 1664 );
1276 1665
1277 1666 $viewings_query = new WP_Query( $args2 );
@@ -1312,8 +1701,9 @@
1312 1701 'post_type' => 'property',
1313 1702 'nopaging' => true,
1314 1703 'post_status' => 'publish',
1315 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.
1316 1706 'meta_query' => array(
1317 1707 'relation' => 'OR',
1318 1708 array(
1319 1709 'key' => '_owner_contact_id',
@@ -1365,8 +1755,9 @@
1365 1755 'post_type' => 'property',
1366 1756 'nopaging' => true,
1367 1757 'post_status' => 'publish',
1368 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.
1369 1760 'meta_query' => array(
1370 1761 'relation' => 'OR',
1371 1762 array(
1372 1763 'key' => '_owner_contact_id',
@@ -1408,9 +1799,11 @@
1408 1799 'fields' => 'ids',
1409 1800 'orderby' => 'meta_value',
1410 1801 'order' => 'DESC',
1411 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.
1412 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.
1413 1806 'meta_query' => array(
1414 1807 array(
1415 1808 'key' => '_property_id',
1416 1809 'value' => $property_ids,
@@ -1422,9 +1815,9 @@
1422 1815 // Do past viewings
1423 1816 $args2 = $args;
1424 1817 $args2['meta_query'][] = array(
1425 1818 'key' => '_start_date_time',
1426 - 'value' => date("Y-m-d H:i:s"),
1819 + 'value' => gmdate("Y-m-d H:i:s"),
1427 1820 'compare' => '<='
1428 1821 );
1429 1822
1430 1823 $viewings_query = new WP_Query( $args2 );
@@ -1445,9 +1838,9 @@
1445 1838 // Do upcoming viewings
1446 1839 $args2 = $args;
1447 1840 $args2['meta_query'][] = array(
1448 1841 'key' => '_start_date_time',
1449 - 'value' => date("Y-m-d H:i:s"),
1842 + 'value' => gmdate("Y-m-d H:i:s"),
1450 1843 'compare' => '>='
1451 1844 );
1452 1845
1453 1846 $viewings_query = new WP_Query( $args2 );
@@ -1481,5 +1874,292 @@
1481 1874 function propertyhive_my_account_delete() {
1482 1875
1483 1876 ph_get_template( 'account/delete.php' );
1484 1877 }
1485 -}
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 +