PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
← All changes | includes/ph-template-functions.php +1149 -116 1.4.62.3.1 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean
3 +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate.
4 +
2 5 /**
3 6 * PropertyHive Template
4 7 *
5 8 * Functions for the templating system.
@@ -17,8 +20,9 @@
17 20 *
18 21 * @param mixed $post
19 22 * @return PH_Property
20 23 */
24 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_setup_property_data; the established callable name is part of the plugin/extension API and must remain stable.
21 25 function ph_setup_property_data( $post ) {
22 26 unset( $GLOBALS['property'] );
23 27
24 28 if ( is_int( $post ) )
@@ -26,8 +30,9 @@
26 30
27 31 if ( empty( $post->post_type ) || ! in_array( $post->post_type, array( 'property' ) ) )
28 32 return;
29 33
34 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared frontend template global; Property Hive intentionally publishes the current property object for templates and builder integrations.
30 35 $GLOBALS['property'] = get_property( $post );
31 36
32 37 return $GLOBALS['property'];
33 38 }
@@ -38,8 +43,9 @@
38 43 *
39 44 * @access public
40 45 * @return void
41 46 */
47 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_properties_rss_feed; the established callable name is part of the plugin/extension API and must remain stable.
42 48 function ph_properties_rss_feed() {
43 49 // Property RSS
44 50 if ( is_post_type_archive( 'property' ) || is_singular( 'property' ) ) {
45 51
@@ -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
@@ -115,9 +166,13 @@
115 166 if ( ( $key = array_search( 'property', $classes ) ) !== false ) {
116 167 unset( $classes[ $key ] );
117 168 }
118 169 $classes[] = 'property';
170 + $classes[] = 'department-' . $property->department;
171 + $classes[] = 'office-' . $property->office_id;
119 172
173 + $classes = array_values($classes);
174 +
120 175 return $classes;
121 176 }
122 177
123 178 /** Global ****************************************************************/
@@ -159,28 +214,39 @@
159 214 * @return string
160 215 */
161 216 function propertyhive_page_title( $echo = true ) {
162 217
163 - if ( is_search() ) {
164 - $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 + );
165 225
166 226 if ( get_query_var( 'paged' ) )
167 - $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 + }
168 234
169 - } elseif ( is_tax() ) {
170 -
235 + }elseif ( is_tax() )
236 + {
171 237 $page_title = single_term_title( "", false );
172 -
173 - } else {
174 -
238 + }
239 + else
240 + {
175 241 $search_results_page_id = ph_get_page_id( 'search_results' );
176 242 $page_title = get_the_title( $search_results_page_id );
177 -
178 243 }
179 244
180 - $page_title = apply_filters( 'propertyhive_page_title', $page_title );
245 + $page_title = apply_filters( 'propertyhive_page_title', wp_kses_post( $page_title ) );
181 246
182 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.
183 249 echo $page_title;
184 250 else
185 251 return $page_title;
186 252 }
@@ -198,8 +264,9 @@
198 264 function propertyhive_property_loop_start( $echo = true ) {
199 265 ob_start();
200 266 ph_get_template( 'search/loop-start.php' );
201 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.
202 269 echo ob_get_clean();
203 270 else
204 271 return ob_get_clean();
205 272 }
@@ -218,8 +285,9 @@
218 285
219 286 ph_get_template( 'search/loop-end.php' );
220 287
221 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.
222 290 echo ob_get_clean();
223 291 else
224 292 return ob_get_clean();
225 293 }
@@ -234,9 +302,10 @@
234 302 * @subpackage Loop
235 303 * @return void
236 304 */
237 305 function propertyhive_template_loop_property_thumbnail() {
238 - 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' ) );
239 308 }
240 309 }
241 310
242 311 if ( ! function_exists( 'propertyhive_get_property_thumbnail' ) ) {
@@ -244,10 +313,10 @@
244 313 /**
245 314 * Get the property thumbnail, or the placeholder if not set.
246 315 *
247 316 * @access public
248 - * @subpackage Loop
249 - * @param string $size (default: 'shop_catalog')
317 + * @subpackage Loop
318 + * @param string $size (default: 'medium')
250 319 * @param int $placeholder_width (default: 0)
251 320 * @param int $placeholder_height (default: 0)
252 321 * @return string
253 322 */
@@ -253,12 +322,12 @@
253 322 */
254 323 function propertyhive_get_property_thumbnail( $size = 'medium', $class = '', $placeholder_width = 0, $placeholder_height = 0 ) {
255 324 global $post, $property;
256 325
257 - $photo_url = $property->get_main_photo_src( $size);
326 + $photo_url = $property->get_main_photo_src( $size );
258 327
259 328 if ($photo_url !== FALSE)
260 - 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 ) . '">';
261 330
262 331 if ( ph_placeholder_img_src() )
263 332 return ph_placeholder_img( $size );
264 333 }
@@ -273,9 +342,14 @@
273 342 * @subpackage Loop
274 343 * @return void
275 344 */
276 345 function propertyhive_template_loop_floor_area() {
277 - 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 + }
278 352 }
279 353 }
280 354
281 355 if ( ! function_exists( 'propertyhive_template_loop_price' ) ) {
@@ -287,9 +361,46 @@
287 361 * @subpackage Loop
288 362 * @return void
289 363 */
290 364 function propertyhive_template_loop_price() {
291 - ph_get_template( 'search/price.php' );
365 +
366 + global $property;
367 +
368 + $fees = '';
369 + if ( get_option('propertyhive_lettings_fees_display_search_results', '') == 'yes' )
370 + {
371 + if (
372 + $property->department == 'residential-lettings' &&
373 + get_option('propertyhive_lettings_fees', '') != ''
374 + )
375 + {
376 + $fees = nl2br(get_option('propertyhive_lettings_fees', ''));
377 + }
378 + if (
379 + $property->department == 'commercial' &&
380 + $property->to_rent == 'yes' &&
381 + get_option('propertyhive_lettings_fees_commercial', '') != ''
382 + )
383 + {
384 + $fees = nl2br(get_option('propertyhive_lettings_fees_commercial', ''));
385 + }
386 + }
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 + }
401 +
402 + ph_get_template( 'search/price.php', array( 'price_qualifier' => $price_qualifier, 'fees' => $fees ) );
292 403 }
293 404 }
294 405
295 406 if ( ! function_exists( 'propertyhive_template_loop_summary' ) ) {
@@ -342,10 +453,26 @@
342 453 * @access public
343 454 * @subpackage Loop
344 455 * @return void
345 456 */
346 - function propertyhive_result_count() {
347 - 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 );
348 475 }
349 476 }
350 477
351 478 if ( ! function_exists( 'propertyhive_catalog_ordering' ) ) {
@@ -356,12 +483,23 @@
356 483 * @access public
357 484 * @subpackage Loop
358 485 * @return void
359 486 */
360 - function propertyhive_catalog_ordering() {
361 - $orderby = isset( $_GET['orderby'] ) ? ph_clean( $_GET['orderby'] ) : apply_filters( 'propertyhive_default_catalog_orderby', get_option( 'propertyhive_default_catalog_orderby' ) );
487 + function propertyhive_catalog_ordering( $department = '', $orderby = '' ) {
362 488
363 - 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 );
364 502 }
365 503 }
366 504
367 505 if ( ! function_exists( 'propertyhive_pagination' ) ) {
@@ -372,15 +510,57 @@
372 510 * @access public
373 511 * @subpackage Loop
374 512 * @return void
375 513 */
376 - function propertyhive_pagination() {
377 - 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 );
378 523 }
379 524 }
380 525
381 -/** Single Product ********************************************************/
526 +/** Single Property ********************************************************/
382 527
528 +if ( ! function_exists( 'propertyhive_template_not_on_market' ) ) {
529 +
530 + /**
531 + * Output a warning/message if property is not on the market
532 + *
533 + * @access public
534 + * @subpackage Property
535 + * @return void
536 + */
537 + function propertyhive_template_not_on_market() {
538 + ph_get_template( 'single-property/not-on-market.php' );
539 + }
540 +}
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 +
383 563 if ( ! function_exists( 'propertyhive_show_property_images' ) ) {
384 564
385 565 /**
386 566 * Output the property image before the single property summary.
@@ -388,10 +568,56 @@
388 568 * @access public
389 569 * @subpackage Property
390 570 * @return void
391 571 */
392 - function propertyhive_show_property_images() {
393 - ph_get_template( 'single-property/property-images.php' );
572 + function propertyhive_show_property_images( $num_images = '' )
573 + {
574 + global $property;
575 +
576 + $images = array();
577 + if ( get_option('propertyhive_images_stored_as', '') == 'urls' )
578 + {
579 + $photo_urls = $property->_photo_urls;
580 + if ( !is_array($photo_urls) ) { $photo_urls = array(); }
581 +
582 + if ( !empty($num_images) )
583 + {
584 + $photo_urls = array_slice($photo_urls, 0, $num_images);
585 + }
586 +
587 + foreach ( $photo_urls as $photo )
588 + {
589 + $images[] = array(
590 + 'title' => isset($photo['title']) ? $photo['title'] : '',
591 + 'url' => isset($photo['url']) ? $photo['url'] : '',
592 + 'image' => '<img src="' . esc_url( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . esc_attr( isset($photo['title']) ? $photo['title'] : '' ) . '">',
593 + );
594 + }
595 + }
596 + else
597 + {
598 + $gallery_attachments = $property->get_gallery_attachment_ids();
599 +
600 + if ( !empty($gallery_attachments) )
601 + {
602 + if ( !empty($num_images) )
603 + {
604 + $gallery_attachments = array_slice($gallery_attachments, 0, $num_images);
605 + }
606 +
607 + foreach ($gallery_attachments as $gallery_attachment)
608 + {
609 + $images[] = array(
610 + 'title' => esc_attr( get_the_title( $gallery_attachment ) ),
611 + 'url' => wp_get_attachment_url( $gallery_attachment ),
612 + 'image' => wp_get_attachment_image( $gallery_attachment, apply_filters( 'propertyhive_single_property_image_size', 'original' ) ),
613 + 'attachment_id' => $gallery_attachment,
614 + );
615 + }
616 + }
617 + }
618 +
619 + ph_get_template( 'single-property/property-images.php', array( 'images' => $images ) );
394 620 }
395 621 }
396 622
397 623 if ( ! function_exists( 'propertyhive_show_property_thumbnails' ) ) {
@@ -403,9 +629,46 @@
403 629 * @subpackage Property
404 630 * @return void
405 631 */
406 632 function propertyhive_show_property_thumbnails() {
407 - ph_get_template( 'single-property/property-thumbnails.php' );
633 +
634 + global $property;
635 +
636 + $images = array();
637 + if ( get_option('propertyhive_images_stored_as', '') == 'urls' )
638 + {
639 + $photo_urls = $property->_photo_urls;
640 + if ( !is_array($photo_urls) ) { $photo_urls = array(); }
641 +
642 + foreach ( $photo_urls as $photo )
643 + {
644 + $images[] = array(
645 + 'title' => isset($photo['title']) ? $photo['title'] : '',
646 + 'url' => isset($photo['url']) ? $photo['url'] : '',
647 + 'image' => '<img src="' . esc_url( isset($photo['url']) ? $photo['url'] : '' ) . '" alt="' . esc_attr( isset($photo['title']) ? $photo['title'] : '' ) . '">',
648 + );
649 + }
650 + }
651 + else
652 + {
653 + $gallery_attachments = $property->get_gallery_attachment_ids();
654 +
655 + if ( !empty($gallery_attachments) )
656 + {
657 + foreach ($gallery_attachments as $gallery_attachment)
658 + {
659 + $images[] = array(
660 + 'title' => esc_attr( get_the_title( $gallery_attachment ) ),
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.
663 + 'image' => wp_get_attachment_image( $gallery_attachment, apply_filters( 'single_property_small_thumbnail_size', 'thumbnail' ) ),
664 + 'attachment_id' => $gallery_attachment,
665 + );
666 + }
667 + }
668 + }
669 +
670 + ph_get_template( 'single-property/property-thumbnails.php', array( 'images' => $images ) );
408 671 }
409 672 }
410 673
411 674 if ( ! function_exists( 'propertyhive_template_single_title' ) ) {
@@ -431,9 +694,14 @@
431 694 * @subpackage Property
432 695 * @return void
433 696 */
434 697 function propertyhive_template_single_floor_area() {
435 - 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 + }
436 704 }
437 705 }
438 706
439 707 if ( ! function_exists( 'propertyhive_template_single_price' ) ) {
@@ -445,9 +713,46 @@
445 713 * @subpackage Property
446 714 * @return void
447 715 */
448 716 function propertyhive_template_single_price() {
449 - ph_get_template( 'single-property/price.php' );
717 +
718 + global $property;
719 +
720 + $fees = '';
721 + if ( get_option('propertyhive_lettings_fees_display_single_property', '') == 'yes' )
722 + {
723 + if (
724 + $property->department == 'residential-lettings' &&
725 + get_option('propertyhive_lettings_fees', '') != ''
726 + )
727 + {
728 + $fees = nl2br(get_option('propertyhive_lettings_fees', ''));
729 + }
730 + if (
731 + $property->department == 'commercial' &&
732 + $property->to_rent == 'yes' &&
733 + get_option('propertyhive_lettings_fees_commercial', '') != ''
734 + )
735 + {
736 + $fees = nl2br(get_option('propertyhive_lettings_fees_commercial', ''));
737 + }
738 + }
739 +
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 ) );
450 755 }
451 756 }
452 757
453 758 if ( ! function_exists( 'propertyhive_template_single_meta' ) ) {
@@ -459,9 +764,180 @@
459 764 * @subpackage Property
460 765 * @return void
461 766 */
462 767 function propertyhive_template_single_meta() {
463 - ph_get_template( 'single-property/meta.php' );
768 +
769 + global $post, $property;
770 +
771 + $meta = array();
772 +
773 + if ( $property->reference_number != '' )
774 + {
775 + $meta['reference-number'] = array(
776 + 'label' => __('Ref', 'propertyhive'),
777 + 'value' => $property->reference_number
778 + );
779 + }
780 +
781 + if ( $property->property_type != '' )
782 + {
783 + $meta['property-type'] = array(
784 + 'label' => __('Type', 'propertyhive'),
785 + 'value' => $property->property_type
786 + );
787 + }
788 +
789 + if ( $property->availability != '' )
790 + {
791 + $meta['availability'] = array(
792 + 'label' => __('Availability', 'propertyhive'),
793 + 'value' => $property->availability
794 + );
795 + }
796 +
797 + if ( $property->department != 'commercial' && ph_get_custom_department_based_on( $property->department ) != 'commercial' )
798 + {
799 + if ( $property->bedrooms > 0 )
800 + {
801 + $meta['bedrooms'] = array(
802 + 'label' => __('Bedrooms', 'propertyhive'),
803 + 'value' => $property->bedrooms
804 + );
805 + }
806 +
807 + if ( $property->bathrooms > 0 )
808 + {
809 + $meta['bathrooms'] = array(
810 + 'label' => __('Bathrooms', 'propertyhive'),
811 + 'value' => $property->bathrooms
812 + );
813 + }
814 +
815 + if ( $property->reception_rooms > 0 )
816 + {
817 + $meta['reception-rooms'] = array(
818 + 'label' => __('Reception Rooms', 'propertyhive'),
819 + 'value' => $property->reception_rooms
820 + );
821 + }
822 +
823 + if ( $property->parking != '' )
824 + {
825 + $meta['parking'] = array(
826 + 'label' => __('Parking', 'propertyhive'),
827 + 'value' => $property->parking
828 + );
829 + }
830 +
831 + if ( $property->outside_space != '' )
832 + {
833 + $meta['outside-space'] = array(
834 + 'label' => __('Outside Space', 'propertyhive'),
835 + 'value' => $property->outside_space
836 + );
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 + }
846 + }
847 +
848 + if ( $property->department == 'residential-sales' || ph_get_custom_department_based_on( $property->department ) == 'residential-sales' )
849 + {
850 + if ( $property->tenure != '' )
851 + {
852 + $meta['tenure'] = array(
853 + 'label' => __('Tenure', 'propertyhive'),
854 + 'value' => $property->tenure
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 + }
907 + }
908 + }
909 +
910 + if ( $property->department == 'residential-lettings' || ph_get_custom_department_based_on( $property->department ) == 'residential-lettings' )
911 + {
912 + if ( $property->furnished != '' )
913 + {
914 + $meta['furnished'] = array(
915 + 'label' => __('Furnished', 'propertyhive'),
916 + 'value' => $property->furnished
917 + );
918 + }
919 +
920 + if ( $property->deposit > 0 )
921 + {
922 + $meta['deposit'] = array(
923 + 'label' => __('Deposit', 'propertyhive'),
924 + 'value' => $property->get_formatted_deposit()
925 + );
926 + }
927 +
928 + if ( $property->available_date != '' )
929 + {
930 + $meta['available-date'] = array(
931 + 'label' => __('Available', 'propertyhive'),
932 + 'value' => $property->get_available_date()
933 + );
934 + }
935 + }
936 +
937 + $meta = apply_filters( 'propertyhive_single_property_meta', $meta );
938 +
939 + ph_get_template( 'single-property/meta.php', array( 'meta' => $meta ) );
464 940 }
465 941 }
466 942
467 943 if ( ! function_exists( 'propertyhive_template_single_sharing' ) ) {
@@ -477,102 +953,286 @@
477 953 ph_get_template( 'single-property/share.php' );
478 954 }
479 955 }
480 956
481 -if ( ! function_exists( 'propertyhive_template_single_actions' ) ) {
957 +if ( ! function_exists( 'propertyhive_get_template_single_floorplans_action' ) ) {
482 958
483 - /**
484 - * Output the product actions (make enquiry etc)
485 - *
486 - * @access public
487 - * @subpackage Property
488 - * @return void
489 - */
490 - function propertyhive_template_single_actions() {
959 + function propertyhive_get_template_single_floorplans_action() {
491 960
492 961 global $post, $property;
493 962
494 963 $actions = array();
495 964
496 - $floorplan_ids = $property->get_floorplan_attachment_ids();
497 - if ( !empty( $floorplan_ids ) )
965 + if ( get_option('propertyhive_floorplans_stored_as', '') == 'urls' )
498 966 {
499 - $floorplans_urls = array();
500 - foreach ($floorplan_ids as $floorplan_id)
967 + $floorplan_urls = $property->_floorplan_urls;
968 + if ( is_array($floorplan_urls) && !empty( $floorplan_urls ) )
501 969 {
502 - $floorplans_urls[] = wp_get_attachment_url( $floorplan_id );
970 + foreach ($floorplan_urls as $floorplan)
971 + {
972 + $actions[] = array(
973 + 'href' => ( ( isset($floorplan['url']) ) ? $floorplan['url'] : '' ),
974 + 'label' => __( 'Floorplan', 'propertyhive' ),
975 + 'class' => 'action-floorplans',
976 + 'attributes' => array(
977 + 'data-fancybox' => 'floorplans'
978 + )
979 + );
980 + }
503 981 }
504 - $actions[] = array(
505 - 'href' => '',
506 - 'label' => __( 'Floorplans', 'propertyhive' ),
507 - 'class' => 'action-floorplans',
508 - 'attributes' => array(
509 - 'data-floorplan-urls' => implode("|", $floorplans_urls)
510 - )
511 - );
512 982 }
983 + else
984 + {
985 + $floorplan_ids = $property->get_floorplan_attachment_ids();
986 + if ( !empty( $floorplan_ids ) )
987 + {
988 + foreach ($floorplan_ids as $floorplan_id)
989 + {
990 + $label = __( 'Floorplan', 'propertyhive' );
513 991
514 - $brochure_ids = $property->get_brochure_attachment_ids();
515 - if ( !empty( $brochure_ids ) )
992 + $attachment_data = wp_prepare_attachment_for_js( $floorplan_id );
993 + if ( isset( $attachment_data['caption'] ) && $attachment_data['caption'] != '' )
994 + {
995 + $label = $attachment_data['caption'];
996 + }
997 +
998 +
999 + $actions[] = array(
1000 + 'href' => wp_get_attachment_url( $floorplan_id ),
1001 + 'label' => $label,
1002 + 'class' => 'action-floorplans',
1003 + 'attributes' => array(
1004 + 'data-fancybox' => 'floorplans'
1005 + )
1006 + );
1007 + }
1008 + }
1009 + }
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 +
1026 + if ( get_option('propertyhive_brochures_stored_as', '') == 'urls' )
516 1027 {
517 - foreach ($brochure_ids as $brochure_id)
1028 + $brochure_urls = $property->_brochure_urls;
1029 + if ( is_array($brochure_urls) && !empty( $brochure_urls ) )
518 1030 {
519 - $actions[] = array(
520 - 'href' => wp_get_attachment_url( $brochure_id ),
521 - 'label' => __( 'View Brochure', 'propertyhive' ),
522 - 'class' => 'action-brochure',
523 - 'attributes' => array(
524 - 'target' => '_blank'
525 - )
526 - );
1031 + foreach ($brochure_urls as $brochure)
1032 + {
1033 + $actions[] = array(
1034 + 'href' => ( ( isset($brochure['url']) ) ? $brochure['url'] : '' ),
1035 + 'label' => __( 'View Brochure', 'propertyhive' ),
1036 + 'class' => 'action-brochure',
1037 + 'attributes' => array(
1038 + 'target' => '_blank'
1039 + )
1040 + );
1041 + }
527 1042 }
528 1043 }
1044 + else
1045 + {
1046 + $brochure_ids = $property->get_brochure_attachment_ids();
1047 + if ( !empty( $brochure_ids ) )
1048 + {
1049 + foreach ($brochure_ids as $brochure_id)
1050 + {
1051 + $actions[] = array(
1052 + 'href' => wp_get_attachment_url( $brochure_id ),
1053 + 'label' => __( 'View Brochure', 'propertyhive' ),
1054 + 'class' => 'action-brochure',
1055 + 'attributes' => array(
1056 + 'target' => '_blank'
1057 + )
1058 + );
1059 + }
1060 + }
1061 + }
529 1062
530 - $epc_ids = $property->get_epc_attachment_ids();
531 - if ( !empty( $epc_ids ) )
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 +
1078 + if ( get_option('propertyhive_epcs_stored_as', '') == 'urls' )
532 1079 {
533 - foreach ($epc_ids as $epc_id)
1080 + $epc_urls = $property->_epc_urls;
1081 + if ( is_array($epc_urls) && !empty( $epc_urls ) )
534 1082 {
535 - $actions[] = array(
536 - 'href' => wp_get_attachment_url( $epc_id ),
537 - 'label' => __( 'View EPC', 'propertyhive' ),
538 - 'class' => 'action-epc',
539 - 'attributes' => array(
540 - 'target' => '_blank'
541 - )
542 - );
1083 + foreach ($epc_urls as $epc)
1084 + {
1085 + $actions[] = array(
1086 + 'href' => ( ( isset($epc['url']) ) ? $epc['url'] : '' ),
1087 + 'label' => __( 'View EPC', 'propertyhive' ),
1088 + 'class' => 'action-epc',
1089 + 'attributes' => array(
1090 + 'target' => '_blank'
1091 + )
1092 + );
1093 + }
543 1094 }
544 1095 }
1096 + else
1097 + {
1098 + $epc_ids = $property->get_epc_attachment_ids();
1099 + if ( !empty( $epc_ids ) )
1100 + {
1101 + foreach ($epc_ids as $epc_id)
1102 + {
1103 + $attributes = array('target' => '_blank');
1104 + if ( wp_attachment_is_image($epc_id) )
1105 + {
1106 + $attributes = array('data-fancybox' => 'epcs');
1107 + }
1108 + $actions[] = array(
1109 + 'href' => wp_get_attachment_url( $epc_id ),
1110 + 'label' => __( 'View EPC', 'propertyhive' ),
1111 + 'class' => 'action-epc',
1112 + 'attributes' => $attributes
1113 + );
1114 + }
1115 + }
1116 + }
545 1117
546 - $virtual_tour_urls = $property->get_virtual_tour_urls();
547 - 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 ) )
548 1135 {
549 - foreach ($virtual_tour_urls as $virtual_tour_url)
1136 + foreach ($virtual_tours as $virtual_tour)
550 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 +
551 1144 $actions[] = array(
552 - 'href' => $virtual_tour_url,
553 - 'label' => __( 'Virtual Tour', 'propertyhive' ),
1145 + 'href' => $virtual_tour['url'],
1146 + 'label' => $virtual_tour['label'],
554 1147 'class' => 'action-virtual-tour',
555 - 'attributes' => array(
556 - 'target' => '_blank'
557 - )
1148 + 'attributes' => $attributes,
558 1149 );
559 1150 }
560 1151 }
561 1152
562 - /*$actions[] = array(
563 - 'href' => '',
564 - 'label' => __( 'View on Map', 'propertyhive' ),
565 - '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 + ),
566 1184 );
567 1185
568 - $actions[] = array(
569 - 'href' => '',
570 - 'label' => __( 'Street View', 'propertyhive' ),
571 - 'class' => 'action-street-view'
572 - );*/
1186 + echo '<div id="ph_material_information" style="display:none;">';
573 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 +
574 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();
575 1235
576 1236 ph_get_template( 'single-property/actions.php', array( 'actions' => $actions ) );
577 1237 }
578 1238 }
@@ -661,8 +1321,10 @@
661 1321 if ( in_array('applicant', $contact_types) )
662 1322 {
663 1323 $applicant_profiles = $contact->applicant_profiles;
664 1324
1325 + $buy_rent = array();
1326 +
665 1327 if ( $applicant_profiles && $applicant_profiles > 0 )
666 1328 {
667 1329 for ( $i = 0; $i < $applicant_profiles; ++$i )
668 1330 {
@@ -669,12 +1331,23 @@
669 1331 $pages['requirements'] = array(
670 1332 'name' => __( 'Requirements', 'propertyhive' ),
671 1333 );
672 1334
673 - 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 + }
674 1345 }
675 1346 }
676 1347
1348 + $buy_rent = array_unique($buy_rent);
1349 +
677 1350 // Check viewing module is active and that viewings exist, either future or past
678 1351 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
679 1352 {
680 1353 $args = array(
@@ -681,8 +1354,9 @@
681 1354 'post_type' => 'viewing',
682 1355 'posts_per_page' => 1,
683 1356 'post_status' => 'publish',
684 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.
685 1359 'meta_query' => array(
686 1360 array(
687 1361 'key' => '_applicant_contact_id',
688 1362 'value' => $contact->id
@@ -693,9 +1367,9 @@
693 1367
694 1368 if ( $viewings_query->have_posts() )
695 1369 {
696 1370 $pages['applicant_viewings'] = array(
697 - 'name' => __( 'Viewings', 'propertyhive' ),
1371 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($buy_rent) ? ' ' . implode('/', $buy_rent) : '' ),
698 1372 );
699 1373 }
700 1374 wp_reset_postdata();
701 1375 }
@@ -701,19 +1375,28 @@
701 1375 }
702 1376 }
703 1377 if ( in_array('owner', $contact_types) )
704 1378 {
1379 + $sell_let = array();
1380 +
705 1381 // Get properties belonging to this owner
706 1382 $args = array(
707 1383 'post_type' => 'property',
708 - 'posts_per_page' => 1,
1384 + 'nopaging' => true,
709 1385 'post_status' => 'publish',
710 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.
711 1388 'meta_query' => array(
1389 + 'relation' => 'OR',
712 1390 array(
713 1391 'key' => '_owner_contact_id',
714 1392 'value' => ':' . $contact->id . ';',
715 1393 'compare' => 'LIKE'
1394 + ),
1395 + array(
1396 + 'key' => '_owner_contact_id',
1397 + 'value' => ':"' . $contact->id . '";',
1398 + 'compare' => 'LIKE'
716 1399 )
717 1400 )
718 1401 );
719 1402
@@ -726,9 +1409,18 @@
726 1409 while ( $properties_query->have_posts() )
727 1410 {
728 1411 $properties_query->the_post();
729 1412
730 - $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 + }
731 1423 }
732 1424
733 1425 $pages['owner_properties'] = array(
734 1426 'name' => __( 'Properties', 'propertyhive' ),
@@ -735,8 +1427,10 @@
735 1427 );
736 1428 }
737 1429 wp_reset_postdata();
738 1430
1431 + $sell_let = array_unique($sell_let);
1432 +
739 1433 // Check viewing module is active and that viewings exist, either future or past
740 1434 if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
741 1435 {
742 1436 $past_viewings = array();
@@ -748,8 +1442,9 @@
748 1442 'post_type' => 'viewing',
749 1443 'posts_per_page' => 1,
750 1444 'post_status' => 'publish',
751 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.
752 1447 'meta_query' => array(
753 1448 array(
754 1449 'key' => '_property_id',
755 1450 'value' => $property_ids,
@@ -761,9 +1456,9 @@
761 1456
762 1457 if ( $viewings_query->have_posts() )
763 1458 {
764 1459 $pages['owner_viewings'] = array(
765 - 'name' => __( 'Viewings', 'propertyhive' ),
1460 + 'name' => __( 'Viewings', 'propertyhive' ) . ( !empty($sell_let) ? ' ' . implode('/', $sell_let) : '' ),
766 1461 );
767 1462 }
768 1463 wp_reset_postdata();
769 1464 }
@@ -770,17 +1465,19 @@
770 1465 }
771 1466 }
772 1467 }
773 1468
774 - // In future we'll add things like 'My properties', viewings if landlord/vendor, documents etc
1469 + $pages = apply_filters( 'propertyhive_my_account_pages', $pages );
775 1470
1471 + /*$pages['delete'] = array(
1472 + 'name' => __( 'Delete Account', 'propertyhive' )
1473 + );*/
1474 +
776 1475 $pages['logout'] = array(
777 1476 'name' => __( 'Logout', 'propertyhive' ),
778 - 'href' => home_url() . '?logout=1' // Logout URL
1477 + 'href' => wp_nonce_url( add_query_arg( 'logout', '1', home_url( '/' ) ), 'log-out' ) // Logout URL
779 1478 );
780 -
781 - $pages = apply_filters( 'propertyhive_my_account_pages', $pages );
782 -
1479 +
783 1480 return $pages;
784 1481 }
785 1482
786 1483 if ( ! function_exists( 'propertyhive_my_account_navigation' ) ) {
@@ -867,19 +1564,36 @@
867 1564 * @return void
868 1565 */
869 1566 function propertyhive_my_account_requirements() {
870 1567
871 - $form_controls = ph_get_applicant_requirements_form_fields();
872 -
873 - $form_controls = apply_filters( 'propertyhive_applicant_requirements_form_fields', $form_controls );
1568 + $user_id = get_current_user_id();
874 1569
875 - // Remove office as this is only required on initial sign up (at the moment anyway)
876 - 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) )
877 1573 {
878 - 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 + }
879 1595 }
880 -
881 - ph_get_template( 'account/requirements.php', array( 'form_controls' => $form_controls ) );
882 1596 }
883 1597 }
884 1598
885 1599 if ( ! function_exists( 'propertyhive_my_account_applicant_viewings' ) ) {
@@ -906,9 +1620,11 @@
906 1620 'fields' => 'ids',
907 1621 'orderby' => 'meta_value',
908 1622 'order' => 'DESC',
909 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.
910 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.
911 1627 'meta_query' => array(
912 1628 array(
913 1629 'key' => '_applicant_contact_id',
914 1630 'value' => $contact->id
@@ -919,9 +1635,9 @@
919 1635 // Do past viewings
920 1636 $args2 = $args;
921 1637 $args2['meta_query'][] = array(
922 1638 'key' => '_start_date_time',
923 - 'value' => date("Y-m-d H:i:s"),
1639 + 'value' => gmdate("Y-m-d H:i:s"),
924 1640 'compare' => '<='
925 1641 );
926 1642
927 1643 $viewings_query = new WP_Query( $args2 );
@@ -942,9 +1658,9 @@
942 1658 // Do upcoming viewings
943 1659 $args2 = $args;
944 1660 $args2['meta_query'][] = array(
945 1661 'key' => '_start_date_time',
946 - 'value' => date("Y-m-d H:i:s"),
1662 + 'value' => gmdate("Y-m-d H:i:s"),
947 1663 'compare' => '>='
948 1664 );
949 1665
950 1666 $viewings_query = new WP_Query( $args2 );
@@ -985,13 +1701,20 @@
985 1701 'post_type' => 'property',
986 1702 'nopaging' => true,
987 1703 'post_status' => 'publish',
988 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.
989 1706 'meta_query' => array(
1707 + 'relation' => 'OR',
990 1708 array(
991 1709 'key' => '_owner_contact_id',
992 1710 'value' => ':' . $contact->id . ';',
993 1711 'compare' => 'LIKE'
1712 + ),
1713 + array(
1714 + 'key' => '_owner_contact_id',
1715 + 'value' => ':"' . $contact->id . '";',
1716 + 'compare' => 'LIKE'
994 1717 )
995 1718 )
996 1719 );
997 1720
@@ -1032,13 +1755,20 @@
1032 1755 'post_type' => 'property',
1033 1756 'nopaging' => true,
1034 1757 'post_status' => 'publish',
1035 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.
1036 1760 'meta_query' => array(
1761 + 'relation' => 'OR',
1037 1762 array(
1038 1763 'key' => '_owner_contact_id',
1039 1764 'value' => ':' . $contact->id . ';',
1040 1765 'compare' => 'LIKE'
1766 + ),
1767 + array(
1768 + 'key' => '_owner_contact_id',
1769 + 'value' => ':"' . $contact->id . '";',
1770 + 'compare' => 'LIKE'
1041 1771 )
1042 1772 )
1043 1773 );
1044 1774
@@ -1069,9 +1799,11 @@
1069 1799 'fields' => 'ids',
1070 1800 'orderby' => 'meta_value',
1071 1801 'order' => 'DESC',
1072 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.
1073 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.
1074 1806 'meta_query' => array(
1075 1807 array(
1076 1808 'key' => '_property_id',
1077 1809 'value' => $property_ids,
@@ -1083,9 +1815,9 @@
1083 1815 // Do past viewings
1084 1816 $args2 = $args;
1085 1817 $args2['meta_query'][] = array(
1086 1818 'key' => '_start_date_time',
1087 - 'value' => date("Y-m-d H:i:s"),
1819 + 'value' => gmdate("Y-m-d H:i:s"),
1088 1820 'compare' => '<='
1089 1821 );
1090 1822
1091 1823 $viewings_query = new WP_Query( $args2 );
@@ -1106,9 +1838,9 @@
1106 1838 // Do upcoming viewings
1107 1839 $args2 = $args;
1108 1840 $args2['meta_query'][] = array(
1109 1841 'key' => '_start_date_time',
1110 - 'value' => date("Y-m-d H:i:s"),
1842 + 'value' => gmdate("Y-m-d H:i:s"),
1111 1843 'compare' => '>='
1112 1844 );
1113 1845
1114 1846 $viewings_query = new WP_Query( $args2 );
@@ -1128,5 +1860,306 @@
1128 1860 }
1129 1861
1130 1862 ph_get_template( 'account/owner-viewings.php', array( 'past_viewings' => $past_viewings, 'upcoming_viewings' => $upcoming_viewings ) );
1131 1863 }
1132 -}
1864 +}
1865 +
1866 +if ( ! function_exists( 'propertyhive_my_account_delete' ) ) {
1867 +
1868 + /**
1869 + * Output the delete section within a users account
1870 + *
1871 + * @access public
1872 + * @return void
1873 + */
1874 + function propertyhive_my_account_delete() {
1875 +
1876 + ph_get_template( 'account/delete.php' );
1877 + }
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 +