PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 1.4.62 All 260 releases
propertyhive / includes / class-ph-shortcodes.php

class-ph-shortcodes.php in Property Hive 2.3.0, at includes/class-ph-shortcodes.php

2,358 lines 77.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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
5
6 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
8 /**
9 * PH_Shortcodes class.
10 *
11 * @class PH_Shortcodes
12 * @version 1.0.0
13 * @package PropertyHive/Classes
14 * @category Class
15 * @author PropertyHive
16 */
17 class PH_Shortcodes {
18
19 /**
20 * Init shortcodes
21 */
22 public static function init() {
23 // Define shortcodes
24 $shortcodes = array(
25 'properties' => __CLASS__ . '::properties',
26 'recent_properties' => __CLASS__ . '::recent_properties',
27 'featured_properties' => __CLASS__ . '::featured_properties',
28 'similar_properties' => __CLASS__ . '::similar_properties',
29 'property_search_form' => __CLASS__ . '::property_search_form',
30 'property_map' => __CLASS__ . '::property_map',
31 'property_static_map' => __CLASS__ . '::property_static_map',
32 'property_street_view' => __CLASS__ . '::property_street_view',
33 'property_office_details' => __CLASS__ . '::property_office_details',
34 'office_map' => __CLASS__ . '::office_map',
35 'applicant_registration_form' => __CLASS__ . '::applicant_registration_form',
36 'propertyhive_my_account' => __CLASS__ . '::my_account',
37 'propertyhive_login_form' => __CLASS__ . '::login_form',
38 'propertyhive_reset_password_form' => __CLASS__ . '::reset_password_form',
39 );
40
41 foreach ( $shortcodes as $shortcode => $function ) {
42 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Public shortcode registration filter; extensions can customize each shortcode tag through this existing dynamic hook contract.
43 add_shortcode( apply_filters( "{$shortcode}_shortcode_tag", $shortcode ), $function );
44 }
45 }
46
47 /**
48 * Shortcode Wrapper
49 *
50 * @param mixed $function
51 * @param array $atts (default: array())
52 * @return string
53 */
54 public static function shortcode_wrapper(
55 $function,
56 $atts = array(),
57 $wrapper = array(
58 'class' => 'propertyhive',
59 'before' => null,
60 'after' => null
61 )
62 ) {
63 ob_start();
64
65 $before = empty( $wrapper['before'] ) ? '<div class="' . esc_attr( $wrapper['class'] ) . '">' : $wrapper['before'];
66 $after = empty( $wrapper['after'] ) ? '</div>' : $wrapper['after'];
67
68 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Wrapper HTML is supplied by PHP callers; the default class is escaped when assembled.
69 echo $before;
70 call_user_func( $function, $atts );
71 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Closing wrapper HTML is supplied by PHP callers.
72 echo $after;
73
74 return ob_get_clean();
75 }
76
77 /**
78 * Output property search form
79 *
80 * @param array $atts
81 * @return string
82 */
83 public static function property_search_form( $atts ) {
84 $atts = shortcode_atts( array(
85 'id' => 'shortcode',
86 'default_department' => ''
87 ), $atts, 'property_search_form' );
88
89 $form_controls = ph_get_search_form_fields();
90
91 $form_controls = apply_filters( 'propertyhive_search_form_fields_' . $atts['id'], $form_controls, $atts );
92 $form_controls = apply_filters( 'propertyhive_search_form_fields', $form_controls, $atts );
93
94 // We 100% need department so make sure it exists. If it doesn't, set a hidden field
95 if ( !isset($form_controls['department']) )
96 {
97 $original_form_controls = ph_get_search_form_fields();
98 $original_department = $original_form_controls['department'];
99 $original_department['type'] = 'hidden';
100
101 $form_controls['department'] = $original_department;
102 }
103
104 $form_controls = apply_filters( 'propertyhive_search_form_fields_after_' . $atts['id'], $form_controls, $atts );
105 $form_controls = apply_filters( 'propertyhive_search_form_fields_after', $form_controls, $atts );
106
107 if (
108 isset($atts['default_department']) && in_array($atts['default_department'], array_keys( ph_get_departments() )) &&
109 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only search form default; no state change.
110 ( !isset($_REQUEST['department']) )
111 )
112 {
113 $form_controls['department']['value'] = $atts['default_department'];
114 }
115
116 ob_start();
117
118 ph_get_template( 'global/search-form.php', array( 'form_controls' => $form_controls, 'id' => $atts['id'] ) );
119
120 return ob_get_clean();
121 }
122
123 /**
124 * List multiple properties shortcode
125 *
126 * @param array $atts
127 * @return string
128 */
129 public static function properties( $atts ) {
130
131 global $propertyhive_loop;
132
133 $atts = shortcode_atts( array(
134 'columns' => '2',
135 'orderby' => 'meta_value_num',
136 'order' => 'desc',
137 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- These lines declare shortcode defaults; they do not pass a meta_key/exclude value to WP_Query or get_posts. 131 is the properties shortcode_atts default meta_key; 133/751/1045/1338 are exclude defaults in shortcode_atts.
138 'meta_key' => '_price_actual',
139 'ids' => '',
140 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- These lines declare shortcode defaults; they do not pass a meta_key/exclude value to WP_Query or get_posts. 131 is the properties shortcode_atts default meta_key; 133/751/1045/1338 are exclude defaults in shortcode_atts.
141 'exclude' => '',
142 'department' => '', // residential-sales / residential-lettings / commercial / any custom department
143 'minimum_price' => '',
144 'maximum_price' => '',
145 'bedrooms' => '',
146 'minimum_bedrooms' => '',
147 'keyword' => '',
148 'address_keyword' => '',
149 'country' => '',
150 'country_not' => '',
151 'availability_id' => '',
152 'marketing_flag' => '', // Deprecated. Use marketing_flag_id instead
153 'marketing_flag_id' => '', // Should be marketing_flag_id. Might deprecate this in the future
154 'property_type_id' => '',
155 'sale_by_id' => '',
156 'location_id' => '',
157 'office_id' => '',
158 'negotiator_id' => '',
159 'commercial_for_sale' => '',
160 'commercial_to_rent' => '',
161 'posts_per_page' => 10,
162 'no_results_output' => '',
163 'pagination' => '',
164 'show_order' => '',
165 'show_result_count' => '',
166 'carousel' => '',
167 ), $atts, 'properties' );
168
169 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
170 {
171 $params = array(
172 'items' => 1,
173 'controlsPosition' => 'bottom',
174 'gutter' => 20,
175 'mouseDrag' => true,
176 'nav' => false,
177 'navPosition' => 'bottom',
178 'controlsText' => array("Prev", "Next"),
179 'responsive' => array(
180 640 => array(
181 'items' => (int)$atts['columns']
182 )
183 )
184 );
185 $params = apply_filters( 'propertyhive_carousel_params', $params );
186 $params = apply_filters( 'propertyhive_properties_carousel_params', $params );
187 wp_localize_script( 'propertyhive_carousel', 'propertyhive_carousel_params', $params );
188
189 wp_enqueue_style( 'tiny_slider_css' );
190 wp_enqueue_script( 'tiny_slider' );
191 wp_enqueue_script( 'propertyhive_carousel' );
192 }
193
194 $meta_query = array(
195 array(
196 'key' => '_on_market',
197 'value' => 'yes',
198 )
199 );
200
201 if ( isset($atts['department']) && in_array($atts['department'], array_keys( ph_get_departments() )) )
202 {
203 $departments = explode(",", $atts['department']);
204 $departments = array_map('trim', $departments);
205 $departments = array_filter($departments);
206
207 $meta_query[] = array(
208 'key' => '_department',
209 'value' => $departments,
210 'compare' => 'IN'
211 );
212 }
213
214 if ( isset($atts['bedrooms']) && $atts['bedrooms'] != '' && is_numeric($atts['bedrooms']) )
215 {
216 $meta_query[] = array(
217 'key' => '_bedrooms',
218 'value' => sanitize_text_field( $atts['bedrooms'] ),
219 'compare' => '=',
220 'type' => 'NUMERIC'
221 );
222 }
223
224 if ( isset($atts['minimum_bedrooms']) && $atts['minimum_bedrooms'] != '' && is_numeric($atts['minimum_bedrooms']) )
225 {
226 $meta_query[] = array(
227 'key' => '_bedrooms',
228 'value' => sanitize_text_field( $atts['minimum_bedrooms'] ),
229 'compare' => '>=',
230 'type' => 'NUMERIC'
231 );
232 }
233
234 $base_department = $atts['department'];
235 if ( $atts['department'] !== '' && !in_array($atts['department'], array_keys( ph_get_departments( true ) )) )
236 {
237 $base_department = ph_get_custom_department_based_on($base_department);
238 }
239
240 if ( isset($atts['department']) && ( $base_department == 'residential-sales' || $base_department == 'residential-lettings' ) && isset($atts['minimum_price']) && $atts['minimum_price'] != '' )
241 {
242 $search_form_currency = get_option( 'propertyhive_search_form_currency', 'GBP' );
243
244 $minimum_price = $atts['minimum_price'];
245 if ( $search_form_currency != 'GBP' )
246 {
247 // Convert $atts['minimum_price'] to GBP
248 $ph_countries = new PH_Countries();
249
250 $minimum_price = $ph_countries->convert_price_to_gbp( $minimum_price, $search_form_currency );
251 }
252
253 $meta_query[] = array(
254 'key' => '_price_actual',
255 'value' => sanitize_text_field( floor( $minimum_price ) ),
256 'compare' => '>=',
257 'type' => 'NUMERIC'
258 );
259 }
260
261 if ( isset($atts['department']) && ( $base_department == 'residential-sales' || $base_department == 'residential-lettings' ) && isset($atts['maximum_price']) && $atts['maximum_price'] != '' )
262 {
263 $search_form_currency = get_option( 'propertyhive_search_form_currency', 'GBP' );
264
265 $maximum_price = $atts['maximum_price'];
266 if ( $search_form_currency != 'GBP' )
267 {
268 // Convert $atts['maximum_price'] to GBP
269 $ph_countries = new PH_Countries();
270
271 $maximum_price = $ph_countries->convert_price_to_gbp( $maximum_price, $search_form_currency );
272 }
273
274 $meta_query[] = array(
275 'key' => '_price_actual',
276 'value' => sanitize_text_field( ceil( $maximum_price ) ),
277 'compare' => '<=',
278 'type' => 'NUMERIC'
279 );
280 }
281
282 if ( isset($atts['address_keyword']) && $atts['address_keyword'] != '' )
283 {
284 $atts['address_keyword'] = ph_clean( trim( $atts['address_keyword'] ) );
285
286 $address_keywords = array( $atts['address_keyword'] );
287
288 if ( strpos( $atts['address_keyword'], ' ' ) !== FALSE )
289 {
290 $address_keywords[] = str_replace(" ", "-", $atts['address_keyword']);
291 }
292 if ( strpos( $atts['address_keyword'], '-' ) !== FALSE )
293 {
294 $address_keywords[] = str_replace("-", " ", $atts['address_keyword']);
295 }
296 if ( strpos( $atts['address_keyword'], '.' ) !== FALSE )
297 {
298 $address_keywords[] = str_replace(".", "", $atts['address_keyword']);
299 }
300 if ( stripos( $atts['address_keyword'], 'st ' ) !== FALSE )
301 {
302 $address_keywords[] = str_ireplace("st ", "st. ", $atts['address_keyword']);
303 }
304 if ( strpos( $atts['address_keyword'], '\'' ) !== FALSE )
305 {
306 $address_keywords[] = str_replace("'", "", $atts['address_keyword']);
307 }
308
309 $sub_meta_query = array('relation' => 'OR');
310
311 $address_keyword_compare = get_option( 'propertyhive_address_keyword_compare', '=' );
312 if ( $address_keyword_compare == 'polygon' )
313 {
314 $address_keyword_compare = apply_filters('propertyhive_shortcode_address_keyword_compare', '=');
315 }
316
317 foreach ( $address_keywords as $address_keyword )
318 {
319 $sub_meta_query[] = array(
320 'key' => '_reference_number',
321 'value' => $address_keyword,
322 'compare' => $address_keyword_compare
323 );
324 $sub_meta_query[] = array(
325 'key' => '_address_street',
326 'value' => $address_keyword,
327 'compare' => $address_keyword_compare
328 );
329 $sub_meta_query[] = array(
330 'key' => '_address_two',
331 'value' => $address_keyword,
332 'compare' => $address_keyword_compare
333 );
334 $sub_meta_query[] = array(
335 'key' => '_address_three',
336 'value' => $address_keyword,
337 'compare' => $address_keyword_compare
338 );
339 $sub_meta_query[] = array(
340 'key' => '_address_four',
341 'value' => $address_keyword,
342 'compare' => $address_keyword_compare
343 );
344 }
345 if ( strlen($atts['address_keyword']) <= 4 )
346 {
347 $sub_meta_query[] = array(
348 'key' => '_address_postcode',
349 'value' => sanitize_text_field( $atts['address_keyword'] ),
350 'compare' => '='
351 );
352 // Run regex match where given keyword is at the start of the postcode ^
353 // followed by one or zero letters (for WC2E-style postcodes) [a-zA-Z]?
354 // then a single space [ ]
355 $sub_meta_query[] = array(
356 'key' => '_address_postcode',
357 'value' => sanitize_text_field( $atts['address_keyword'] ) . '[a-zA-Z]?[ ]',
358 'compare' => 'RLIKE'
359 );
360 }
361 else
362 {
363 $sub_meta_query[] = array(
364 'key' => '_address_postcode',
365 'value' => sanitize_text_field( $atts['address_keyword'] ),
366 'compare' => 'LIKE'
367 );
368 }
369
370 $meta_query[] = $sub_meta_query;
371 }
372
373 if ( isset($atts['keyword']) && $atts['keyword'] != '' )
374 {
375 $atts['keyword'] = sanitize_text_field( trim( $atts['keyword'] ) );
376
377 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Preserve the original request verbatim only to restore it after this shortcode's query; it is not output or persisted.
378 $original_keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
379 $_REQUEST['keyword'] = $atts['keyword'];
380
381 add_filter( 'posts_where', array( PH()->query, 'keyword_excerpt_where' ), 10, 2 );
382
383 $keywords = array( $atts['keyword'] );
384
385 if ( strpos( $atts['keyword'], ' ' ) !== FALSE )
386 {
387 $keywords[] = str_replace(" ", "-", $atts['keyword']);
388 }
389 if ( strpos( $atts['keyword'], '-' ) !== FALSE )
390 {
391 $keywords[] = str_replace("-", " ", $atts['keyword']);
392 }
393 if ( strpos( $atts['keyword'], '.' ) !== FALSE )
394 {
395 $keywords[] = str_replace(".", "", $atts['keyword']);
396 }
397 if ( stripos( $atts['keyword'], 'st ' ) !== FALSE )
398 {
399 $keywords[] = str_ireplace("st ", "st. ", $atts['keyword']);
400 }
401 if ( strpos( $atts['keyword'], '\'' ) !== FALSE )
402 {
403 $keywords[] = str_replace("'", "", $atts['keyword']);
404 }
405
406 $sub_meta_query = array('relation' => 'OR');
407
408 $address_keyword_compare = get_option( 'propertyhive_address_keyword_compare', '=' );
409 if ( $address_keyword_compare == 'polygon' )
410 {
411 $address_keyword_compare = apply_filters('propertyhive_shortcode_address_keyword_compare', '=');
412 }
413
414 foreach ( $keywords as $keyword )
415 {
416 $sub_meta_query[] = array(
417 'key' => '_reference_number',
418 'value' => $keyword,
419 'compare' => $address_keyword_compare
420 );
421 $sub_meta_query[] = array(
422 'key' => '_address_street',
423 'value' => $keyword,
424 'compare' => $address_keyword_compare
425 );
426 $sub_meta_query[] = array(
427 'key' => '_address_two',
428 'value' => $keyword,
429 'compare' => $address_keyword_compare
430 );
431 $sub_meta_query[] = array(
432 'key' => '_address_three',
433 'value' => $keyword,
434 'compare' => $address_keyword_compare
435 );
436 $sub_meta_query[] = array(
437 'key' => '_address_four',
438 'value' => $keyword,
439 'compare' => $address_keyword_compare
440 );
441 $sub_meta_query[] = array(
442 'key' => '_features_concatenated',
443 'value' => $keyword,
444 'compare' => 'LIKE'
445 );
446 $sub_meta_query[] = array(
447 'key' => '_descriptions_concatenated',
448 'value' => $keyword,
449 'compare' => 'LIKE'
450 );
451 }
452 if ( strlen($atts['keyword']) <= 4 )
453 {
454 $sub_meta_query[] = array(
455 'key' => '_address_postcode',
456 'value' => sanitize_text_field( $atts['keyword'] ),
457 'compare' => '='
458 );
459 // Run regex match where given keyword is at the start of the postcode ^
460 // followed by one or zero letters (for WC2E-style postcodes) [a-zA-Z]?
461 // then a single space [ ]
462 $sub_meta_query[] = array(
463 'key' => '_address_postcode',
464 'value' => sanitize_text_field( $atts['keyword'] ) . '[a-zA-Z]?[ ]',
465 'compare' => 'RLIKE'
466 );
467 }
468 else
469 {
470 $sub_meta_query[] = array(
471 'key' => '_address_postcode',
472 'value' => sanitize_text_field( $atts['keyword'] ),
473 'compare' => 'LIKE'
474 );
475 }
476
477 $meta_query[] = $sub_meta_query;
478
479 $_REQUEST['keyword'] = $original_keyword; // reset back in case it's used elsewhere
480 }
481
482 if ( isset($atts['country']) && $atts['country'] != '' )
483 {
484 $meta_query[] = array(
485 'key' => '_address_country',
486 'value' => sanitize_text_field( $atts['country'] ),
487 'compare' => '=',
488 );
489 }
490
491 if ( isset($atts['country_not']) && $atts['country_not'] != '' )
492 {
493 $meta_query[] = array(
494 'key' => '_address_country',
495 'value' => sanitize_text_field( $atts['country_not'] ),
496 'compare' => '!=',
497 );
498 }
499
500 if ( isset($atts['office_id']) && $atts['office_id'] != '' )
501 {
502 $meta_query[] = array(
503 'key' => '_office_id',
504 'value' => explode(",", $atts['office_id']),
505 'compare' => 'IN',
506 );
507 }
508
509 if ( isset($atts['negotiator_id']) && $atts['negotiator_id'] != '' )
510 {
511 $meta_query[] = array(
512 'key' => '_negotiator_id',
513 'value' => explode(",", $atts['negotiator_id']),
514 'compare' => 'IN',
515 );
516 }
517
518 if ( isset($atts['commercial_for_sale']) && $atts['commercial_for_sale'] != '' )
519 {
520 $meta_query[] = array(
521 'key' => '_for_sale',
522 'value' => 'yes',
523 'compare' => '=',
524 );
525 }
526
527 if ( isset($atts['commercial_to_rent']) && $atts['commercial_to_rent'] != '' )
528 {
529 $meta_query[] = array(
530 'key' => '_to_rent',
531 'value' => 'yes',
532 'compare' => '=',
533 );
534 }
535
536 $tax_query = array();
537
538 if ( isset($atts['availability_id']) && $atts['availability_id'] != '' )
539 {
540 $tax_query[] = array(
541 'taxonomy' => 'availability',
542 'terms' => explode(",", $atts['availability_id']),
543 'compare' => 'IN',
544 );
545 }
546
547 // Fallback for deprecated marketing_flag
548 if ( isset($atts['marketing_flag']) && $atts['marketing_flag'] != '' )
549 {
550 $atts['marketing_flag_id'] = $atts['marketing_flag'];
551 }
552 if ( isset($atts['marketing_flag_id']) && $atts['marketing_flag_id'] != '' )
553 {
554 $tax_query[] = array(
555 'taxonomy' => 'marketing_flag',
556 'terms' => explode(",", $atts['marketing_flag_id']),
557 'compare' => 'IN',
558 );
559 }
560
561 if ( isset($atts['property_type_id']) && $atts['property_type_id'] != '' )
562 {
563 // Change field to check when department is specified as commercial, or if commercial is the only active department
564 if (
565 ( isset($atts['department']) && $base_department == 'commercial' ) ||
566 (
567 !isset($atts['department']) &&
568 get_option( 'propertyhive_active_departments_sales' ) != 'yes' &&
569 get_option( 'propertyhive_active_departments_lettings' ) != 'yes' &&
570 get_option( 'propertyhive_active_departments_commercial' ) == 'yes'
571 )
572 )
573 {
574 $tax_query[] = array(
575 'taxonomy' => 'commercial_property_type',
576 'terms' => explode(",", $atts['property_type_id']),
577 'compare' => 'IN',
578 );
579 }
580 else
581 {
582 $tax_query[] = array(
583 'taxonomy' => 'property_type',
584 'terms' => explode(",", $atts['property_type_id']),
585 'compare' => 'IN',
586 );
587 }
588 }
589
590 if ( isset($atts['location_id']) && $atts['location_id'] != '' )
591 {
592 $tax_query[] = array(
593 'taxonomy' => 'location',
594 'terms' => explode(",", $atts['location_id']),
595 'compare' => 'IN',
596 );
597 }
598
599 if ( isset($atts['sale_by_id']) && $atts['sale_by_id'] != '' )
600 {
601 $tax_query[] = array(
602 'taxonomy' => 'sale_by',
603 'terms' => explode(",", $atts['sale_by_id']),
604 'compare' => 'IN',
605 );
606 }
607
608 // Change default meta key when department is specified as commercial, or if commercial is the only active department
609 if (
610 ( isset($atts['department']) && $base_department == 'commercial' ) ||
611 (
612 get_option( 'propertyhive_active_departments_sales' ) != 'yes' &&
613 get_option( 'propertyhive_active_departments_lettings' ) != 'yes' &&
614 get_option( 'propertyhive_active_departments_commercial' ) == 'yes'
615 )
616 )
617 {
618 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
619 $atts['meta_key'] = '_floor_area_from_sqft';
620 }
621
622 // Get which page we're currently viewing from the URL
623 $paged = max( 1, get_query_var( 'paged' ) );
624
625 $args = array(
626 'post_type' => 'property',
627 'post_status' => ( ( is_user_logged_in() && current_user_can( 'manage_propertyhive' ) ) ? array('publish', 'private') : 'publish' ),
628 'ignore_sticky_posts' => 1,
629 'orderby' => $atts['orderby'],
630 'order' => $atts['order'],
631 'posts_per_page' => $atts['posts_per_page'],
632 'paged' => $paged,
633 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Property eligibility and matching fields live in the established metadata schema; retain these filters and the shortcode page limit.
634 'meta_query' => $meta_query,
635 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Property taxonomy filters are required by this shortcode; WordPress builds the query and the shortcode page limit is retained.
636 'tax_query' => $tax_query,
637 'has_password' => false,
638 );
639 if ( ! empty( $atts['meta_key'] ) ) {
640 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
641 $args['meta_key'] = $atts['meta_key'];
642 }
643
644 if ( ! empty( $atts['ids'] ) )
645 {
646 $include_ids = array_map( 'absint', explode( ',', $atts['ids'] ) );
647 $include_ids = array_filter( $include_ids );
648 if ( ! empty( $include_ids ) )
649 {
650 $args['post__in'] = $include_ids;
651 }
652 }
653 if ( ! empty( $atts['exclude'] ) )
654 {
655 $exclude_ids = array_map( 'absint', explode( ',', $atts['exclude'] ) );
656 $exclude_ids = array_filter( $exclude_ids );
657 if ( ! empty( $exclude_ids ) ) {
658 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Explicit shortcode exclusions are validated as integer IDs above; retain this published selection feature within the shortcode page limit.
659 $args['post__not_in'] = $exclude_ids;
660 }
661 }
662 if ( isset($atts['orderby']) && $atts['orderby'] == 'date' )
663 {
664 $args['orderby'] = 'meta_value';
665 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
666 $args['meta_key'] = '_on_market_change_date';
667 }
668
669 $args['orderby'] .= ' post_title';
670
671 ob_start();
672
673 do_action('propertyhive_shortcode_properties_before_catalog_ordering', $atts);
674
675 if ( isset($atts['show_order']) && $atts['show_order'] != '' )
676 {
677 list( $args, $orderby ) = self::get_show_order_args( $atts, $args );
678
679 propertyhive_catalog_ordering( $atts['department'], $orderby );
680 }
681
682 do_action('propertyhive_shortcode_properties_after_catalog_ordering', $atts);
683
684 $args = apply_filters( 'propertyhive_properties_query', $args, $atts );
685 $args = apply_filters( 'propertyhive_shortcode_properties_query', $args, $atts );
686
687 $properties = new WP_Query( $args );
688
689 if ( isset($atts['show_result_count']) && $atts['show_result_count'] != '' )
690 {
691 $total_posts = $properties->found_posts;
692
693 $first = ( $atts['posts_per_page'] * $paged ) - $atts['posts_per_page'] + 1;
694 $last = min( $total_posts, $atts['posts_per_page'] * $paged );
695
696 propertyhive_result_count( $paged, $atts['posts_per_page'], $total_posts, $first, $last);
697 }
698
699 do_action('propertyhive_shortcode_properties_after_result_count', $atts);
700
701 $propertyhive_loop['columns'] = (int)$atts['columns'];
702
703 if ( $properties->have_posts() ) : ?>
704
705 <?php
706 ob_start();
707 propertyhive_property_loop_start();
708 $loop_start = ob_get_clean();
709 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
710 {
711 $loop_start = str_replace("class=\"properties", "class=\"properties propertyhive-shortcode-carousel", $loop_start);
712 }
713 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Buffered loop template HTML; preserve theme overrides and the static carousel class insertion.
714 echo $loop_start;
715 ?>
716
717 <?php while ( $properties->have_posts() ) : $properties->the_post(); ?>
718
719 <?php ph_get_template_part( 'content', 'property' ); ?>
720
721 <?php endwhile; // end of the loop. ?>
722
723 <?php propertyhive_property_loop_end(); ?>
724
725 <?php else: ?>
726
727 <p class="propertyhive-info no-results-message"><?php echo wp_kses_post($atts['no_results_output']); ?></p>
728
729 <?php endif;
730
731 if ( isset($atts['pagination']) && $atts['pagination'] != '' )
732 {
733 propertyhive_pagination( $properties->max_num_pages );
734 }
735
736 wp_reset_postdata();
737
738 $shortcode_output = ob_get_clean();
739
740 return apply_filters( 'propertyhive_properties_shortcode_output', '<div class="propertyhive propertyhive-properties-shortcode columns-' . (int)$atts['columns'] . '">' . $shortcode_output . '</div>', $shortcode_output );
741 }
742
743 /**
744 * Recent Properties shortcode
745 *
746 * @access public
747 * @param array $atts
748 * @return string
749 */
750 public static function recent_properties( $atts ) {
751 global $propertyhive_loop;
752
753 $atts = shortcode_atts( array(
754 'per_page' => '12',
755 'columns' => '4',
756 'department' => '',
757 'minimum_price' => '',
758 'office_id' => '',
759 'negotiator_id' => '',
760 'availability_id' => '',
761 'marketing_flag_id' => '',
762 'property_type_id' => '',
763 'sale_by_id' => '',
764 'location_id' => '',
765 'commercial_for_sale' => '',
766 'commercial_to_rent' => '',
767 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- These lines declare shortcode defaults; they do not pass a meta_key/exclude value to WP_Query or get_posts. 131 is the properties shortcode_atts default meta_key; 133/751/1045/1338 are exclude defaults in shortcode_atts.
768 'exclude' => '',
769 'orderby' => 'date',
770 'order' => 'desc',
771 'no_results_output' => '',
772 'pagination' => '',
773 'show_order' => '',
774 'show_result_count' => '',
775 'carousel' => '',
776 ), $atts, 'recent_properties' );
777
778 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
779 {
780 $params = array(
781 'items' => 1,
782 'controlsPosition' => 'bottom',
783 'gutter' => 20,
784 'mouseDrag' => true,
785 'nav' => false,
786 'navPosition' => 'bottom',
787 'controlsText' => array("Prev", "Next"),
788 'responsive' => array(
789 640 => array(
790 'items' => (int)$atts['columns']
791 )
792 )
793 );
794 $params = apply_filters( 'propertyhive_carousel_params', $params );
795 $params = apply_filters( 'propertyhive_recent_properties_carousel_params', $params );
796 wp_localize_script( 'propertyhive_carousel', 'propertyhive_carousel_params', $params );
797
798 wp_enqueue_style( 'tiny_slider_css' );
799 wp_enqueue_script( 'tiny_slider' );
800 wp_enqueue_script( 'propertyhive_carousel' );
801 }
802
803 $meta_query = PH()->query->get_meta_query();
804
805 if ( isset($atts['department']) && $atts['department'] != '' )
806 {
807 $departments = explode(",", $atts['department']);
808 $departments = array_map('trim', $departments);
809 $departments = array_filter($departments);
810
811 $meta_query[] = array(
812 'key' => '_department',
813 'value' => $departments,
814 'compare' => 'IN'
815 );
816 }
817
818 $base_department = $atts['department'];
819 if ( $atts['department'] !== '' && !in_array($atts['department'], array_keys( ph_get_departments( true ) )) )
820 {
821 $base_department = ph_get_custom_department_based_on($base_department);
822 }
823
824 if ( isset($atts['department']) && $base_department == 'residential-sales' && isset($atts['minimum_price']) && $atts['minimum_price'] != '' )
825 {
826 $search_form_currency = get_option( 'propertyhive_search_form_currency', 'GBP' );
827
828 $minimum_price = $atts['minimum_price'];
829 if ( $search_form_currency != 'GBP' )
830 {
831 // Convert $atts['minimum_price'] to GBP
832 $ph_countries = new PH_Countries();
833
834 $minimum_price = $ph_countries->convert_price_to_gbp( $minimum_price, $search_form_currency );
835 }
836
837 $meta_query[] = array(
838 'key' => '_price_actual',
839 'value' => sanitize_text_field( floor( $minimum_price ) ),
840 'compare' => '>=',
841 'type' => 'NUMERIC'
842 );
843 }
844
845 if ( isset($atts['office_id']) && $atts['office_id'] != '' )
846 {
847 $meta_query[] = array(
848 'key' => '_office_id',
849 'value' => explode(",", $atts['office_id']),
850 'compare' => 'IN'
851 );
852 }
853
854 if ( isset($atts['negotiator_id']) && $atts['negotiator_id'] != '' )
855 {
856 $meta_query[] = array(
857 'key' => '_negotiator_id',
858 'value' => explode(",", $atts['negotiator_id']),
859 'compare' => 'IN',
860 );
861 }
862
863 if ( isset($atts['commercial_for_sale']) && $atts['commercial_for_sale'] != '' )
864 {
865 $meta_query[] = array(
866 'key' => '_for_sale',
867 'value' => 'yes',
868 'compare' => '=',
869 );
870 }
871
872 if ( isset($atts['commercial_to_rent']) && $atts['commercial_to_rent'] != '' )
873 {
874 $meta_query[] = array(
875 'key' => '_to_rent',
876 'value' => 'yes',
877 'compare' => '=',
878 );
879 }
880
881 $tax_query = array();
882
883 if ( isset($atts['availability_id']) && $atts['availability_id'] != '' )
884 {
885 $tax_query[] = array(
886 'taxonomy' => 'availability',
887 'terms' => explode(",", $atts['availability_id']),
888 'compare' => 'IN',
889 );
890 }
891
892 if ( isset($atts['marketing_flag_id']) && $atts['marketing_flag_id'] != '' )
893 {
894 $tax_query[] = array(
895 'taxonomy' => 'marketing_flag',
896 'terms' => explode(",", $atts['marketing_flag_id']),
897 'compare' => 'IN',
898 );
899 }
900
901 if ( isset($atts['property_type_id']) && $atts['property_type_id'] != '' )
902 {
903 // Change field to check when department is specified as commercial, or if commercial is the only active department
904 if (
905 ( isset($atts['department']) && $base_department == 'commercial' ) ||
906 (
907 !isset($atts['department']) &&
908 get_option( 'propertyhive_active_departments_sales' ) != 'yes' &&
909 get_option( 'propertyhive_active_departments_lettings' ) != 'yes' &&
910 get_option( 'propertyhive_active_departments_commercial' ) == 'yes'
911 )
912 )
913 {
914 $tax_query[] = array(
915 'taxonomy' => 'commercial_property_type',
916 'terms' => explode(",", $atts['property_type_id']),
917 'compare' => 'IN',
918 );
919 }
920 else
921 {
922 $tax_query[] = array(
923 'taxonomy' => 'property_type',
924 'terms' => explode(",", $atts['property_type_id']),
925 'compare' => 'IN',
926 );
927 }
928 }
929
930 if ( isset($atts['location_id']) && $atts['location_id'] != '' )
931 {
932 $tax_query[] = array(
933 'taxonomy' => 'location',
934 'terms' => explode(",", $atts['location_id']),
935 'compare' => 'IN',
936 );
937 }
938
939 if ( isset($atts['sale_by_id']) && $atts['sale_by_id'] != '' )
940 {
941 $tax_query[] = array(
942 'taxonomy' => 'sale_by',
943 'terms' => explode(",", $atts['sale_by_id']),
944 'compare' => 'IN',
945 );
946 }
947
948 // Get which page we're currently viewing from the URL
949 $paged = max( 1, get_query_var( 'paged' ) );
950
951 $args = array(
952 'post_type' => 'property',
953 'post_status' => ( ( is_user_logged_in() && current_user_can( 'manage_propertyhive' ) ) ? array('publish', 'private') : 'publish' ),
954 'ignore_sticky_posts' => 1,
955 'posts_per_page' => $atts['per_page'],
956 'paged' => $paged,
957 'orderby' => $atts['orderby'],
958 'order' => $atts['order'],
959 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Property eligibility and matching fields live in the established metadata schema; retain these filters and the shortcode page limit.
960 'meta_query' => $meta_query,
961 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Property taxonomy filters are required by this shortcode; WordPress builds the query and the shortcode page limit is retained.
962 'tax_query' => $tax_query,
963 'has_password' => false,
964 );
965
966 if ( ! empty( $atts['exclude'] ) )
967 {
968 $exclude_ids = array_map( 'absint', explode( ',', $atts['exclude'] ) );
969 $exclude_ids = array_filter( $exclude_ids );
970 if ( ! empty( $exclude_ids ) ) {
971 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Explicit shortcode exclusions are validated as integer IDs above; retain this published selection feature within the shortcode page limit.
972 $args['post__not_in'] = $exclude_ids;
973 }
974 }
975
976 if ( isset($atts['orderby']) && $atts['orderby'] == 'date' )
977 {
978 $args['orderby'] = 'meta_value';
979 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
980 $args['meta_key'] = '_on_market_change_date';
981 }
982
983 $args['orderby'] .= ' post_title';
984
985 ob_start();
986
987 if ( isset($atts['show_order']) && $atts['show_order'] != '' )
988 {
989 list( $args, $orderby ) = self::get_show_order_args( $atts, $args );
990
991 propertyhive_catalog_ordering( $atts['department'], $orderby );
992 }
993
994 $properties = new WP_Query( apply_filters( 'propertyhive_shortcode_recent_properties_query', $args, $atts ) );
995
996 if ( isset($atts['show_result_count']) && $atts['show_result_count'] != '' )
997 {
998 $total_posts = $properties->found_posts;
999
1000 $first = ( $atts['per_page'] * $paged ) - $atts['per_page'] + 1;
1001 $last = min( $total_posts, $atts['per_page'] * $paged );
1002
1003 propertyhive_result_count( $paged, $atts['per_page'], $total_posts, $first, $last);
1004 }
1005
1006 $propertyhive_loop['columns'] = (int)$atts['columns'];
1007
1008 if ( $properties->have_posts() ) : ?>
1009
1010 <?php
1011 ob_start();
1012 propertyhive_property_loop_start();
1013 $loop_start = ob_get_clean();
1014 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
1015 {
1016 $loop_start = str_replace("class=\"properties", "class=\"properties propertyhive-shortcode-carousel", $loop_start);
1017 }
1018 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Buffered loop template HTML; preserve theme overrides and the static carousel class insertion.
1019 echo $loop_start;
1020 ?>
1021
1022 <?php while ( $properties->have_posts() ) : $properties->the_post(); ?>
1023
1024 <?php ph_get_template_part( 'content', 'property-recent' ); ?>
1025
1026 <?php endwhile; // end of the loop. ?>
1027
1028 <?php propertyhive_property_loop_end(); ?>
1029
1030 <?php else: ?>
1031
1032 <p class="propertyhive-info no-results-message"><?php echo wp_kses_post($atts['no_results_output']); ?></p>
1033
1034 <?php endif;
1035
1036 if ( isset($atts['pagination']) && $atts['pagination'] != '' )
1037 {
1038 propertyhive_pagination( $properties->max_num_pages );
1039 }
1040
1041 wp_reset_postdata();
1042
1043 $shortcode_output = ob_get_clean();
1044
1045 return apply_filters( 'propertyhive_recent_properties_shortcode_output', '<div class="propertyhive propertyhive-recent-properties-shortcode columns-' . (int)$atts['columns'] . '">' . $shortcode_output . '</div>', $shortcode_output );
1046
1047 }
1048
1049 /**
1050 * Output featured properties
1051 *
1052 * @access public
1053 * @param array $atts
1054 * @return string
1055 */
1056 public static function featured_properties( $atts ) {
1057 global $propertyhive_loop;
1058
1059 $atts = shortcode_atts( array(
1060 'per_page' => '12',
1061 'columns' => '4',
1062 'department' => '',
1063 'address_keyword' => '',
1064 'office_id' => '',
1065 'negotiator_id' => '',
1066 'availability_id' => '',
1067 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- These lines declare shortcode defaults; they do not pass a meta_key/exclude value to WP_Query or get_posts. 131 is the properties shortcode_atts default meta_key; 133/751/1045/1338 are exclude defaults in shortcode_atts.
1068 'exclude' => '',
1069 'orderby' => 'rand',
1070 'order' => 'desc',
1071 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Shortcode default declaration only; no query executes here.
1072 'meta_key' => '',
1073 'no_results_output' => '',
1074 'pagination' => '',
1075 'show_order' => '',
1076 'show_result_count' => '',
1077 'carousel' => '',
1078 ), $atts, 'featured_properties' );
1079
1080 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
1081 {
1082 $params = array(
1083 'items' => 1,
1084 'controlsPosition' => 'bottom',
1085 'gutter' => 20,
1086 'mouseDrag' => true,
1087 'nav' => false,
1088 'navPosition' => 'bottom',
1089 'controlsText' => array("Prev", "Next"),
1090 'responsive' => array(
1091 640 => array(
1092 'items' => (int)$atts['columns']
1093 )
1094 )
1095 );
1096 $params = apply_filters( 'propertyhive_carousel_params', $params );
1097 $params = apply_filters( 'propertyhive_featured_properties_carousel_params', $params );
1098 wp_localize_script( 'propertyhive_carousel', 'propertyhive_carousel_params', $params );
1099
1100 wp_enqueue_style( 'tiny_slider_css' );
1101 wp_enqueue_script( 'tiny_slider' );
1102 wp_enqueue_script( 'propertyhive_carousel' );
1103 }
1104
1105 // Get which page we're currently viewing from the URL
1106 $paged = max( 1, get_query_var( 'paged' ) );
1107
1108 $args = array(
1109 'post_type' => 'property',
1110 'post_status' => ( ( is_user_logged_in() && current_user_can( 'manage_propertyhive' ) ) ? array('publish', 'private') : 'publish' ),
1111 'ignore_sticky_posts' => 1,
1112 'posts_per_page' => $atts['per_page'],
1113 'paged' => $paged,
1114 'orderby' => $atts['orderby'],
1115 'order' => $atts['order'],
1116 'has_password' => false,
1117 );
1118
1119 $meta_query = array(
1120 array(
1121 'key' => '_on_market',
1122 'value' => 'yes',
1123 ),
1124 array(
1125 'key' => '_featured',
1126 'value' => 'yes'
1127 )
1128 );
1129
1130 if ( isset($atts['department']) && $atts['department'] != '' )
1131 {
1132 $departments = explode(",", $atts['department']);
1133 $departments = array_map('trim', $departments);
1134 $departments = array_filter($departments);
1135
1136 $meta_query[] = array(
1137 'key' => '_department',
1138 'value' => $departments,
1139 'compare' => 'IN'
1140 );
1141 }
1142
1143 if ( isset($atts['address_keyword']) && $atts['address_keyword'] != '' )
1144 {
1145 $atts['address_keyword'] = sanitize_text_field( trim( $atts['address_keyword'] ) );
1146
1147 $address_keywords = array( $atts['address_keyword'] );
1148
1149 if ( strpos( $atts['address_keyword'], ' ' ) !== FALSE )
1150 {
1151 $address_keywords[] = str_replace(" ", "-", $atts['address_keyword']);
1152 }
1153 if ( strpos( $atts['address_keyword'], '-' ) !== FALSE )
1154 {
1155 $address_keywords[] = str_replace("-", " ", $atts['address_keyword']);
1156 }
1157
1158 $sub_meta_query = array('relation' => 'OR');
1159
1160 $address_keyword_compare = get_option( 'propertyhive_address_keyword_compare', '=' );
1161 if ( $address_keyword_compare == 'polygon' )
1162 {
1163 $address_keyword_compare = apply_filters('propertyhive_shortcode_address_keyword_compare', '=');
1164 }
1165
1166 foreach ( $address_keywords as $address_keyword )
1167 {
1168 $sub_meta_query[] = array(
1169 'key' => '_reference_number',
1170 'value' => $address_keyword,
1171 'compare' => $address_keyword_compare
1172 );
1173 $sub_meta_query[] = array(
1174 'key' => '_address_street',
1175 'value' => $address_keyword,
1176 'compare' => $address_keyword_compare
1177 );
1178 $sub_meta_query[] = array(
1179 'key' => '_address_two',
1180 'value' => $address_keyword,
1181 'compare' => $address_keyword_compare
1182 );
1183 $sub_meta_query[] = array(
1184 'key' => '_address_three',
1185 'value' => $address_keyword,
1186 'compare' => $address_keyword_compare
1187 );
1188 $sub_meta_query[] = array(
1189 'key' => '_address_four',
1190 'value' => $address_keyword,
1191 'compare' => $address_keyword_compare
1192 );
1193 }
1194 if ( strlen($atts['address_keyword']) <= 4 )
1195 {
1196 $sub_meta_query[] = array(
1197 'key' => '_address_postcode',
1198 'value' => sanitize_text_field( $atts['address_keyword'] ),
1199 'compare' => '='
1200 );
1201 // Run regex match where given keyword is at the start of the postcode ^
1202 // followed by one or zero letters (for WC2E-style postcodes) [a-zA-Z]?
1203 // then a single space [ ]
1204 $sub_meta_query[] = array(
1205 'key' => '_address_postcode',
1206 'value' => sanitize_text_field( $atts['address_keyword'] ) . '[a-zA-Z]?[ ]',
1207 'compare' => 'RLIKE'
1208 );
1209 }
1210 else
1211 {
1212 $sub_meta_query[] = array(
1213 'key' => '_address_postcode',
1214 'value' => sanitize_text_field( $atts['address_keyword'] ),
1215 'compare' => 'LIKE'
1216 );
1217 }
1218
1219 $meta_query[] = $sub_meta_query;
1220 }
1221
1222 if ( isset($atts['office_id']) && $atts['office_id'] != '' )
1223 {
1224 $meta_query[] = array(
1225 'key' => '_office_id',
1226 'value' => explode(",", $atts['office_id']),
1227 'compare' => 'IN'
1228 );
1229 }
1230
1231 if ( isset($atts['negotiator_id']) && $atts['negotiator_id'] != '' )
1232 {
1233 $meta_query[] = array(
1234 'key' => '_negotiator_id',
1235 'value' => explode(",", $atts['negotiator_id']),
1236 'compare' => 'IN',
1237 );
1238 }
1239
1240 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Property eligibility and matching fields live in the established metadata schema; retain these filters and the shortcode page limit.
1241 $args['meta_query'] = $meta_query;
1242
1243 if ( ! empty( $atts['meta_key'] ) ) {
1244 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
1245 $args['meta_key'] = $atts['meta_key'];
1246 }
1247
1248 $tax_query = array();
1249
1250 if ( isset($atts['availability_id']) && $atts['availability_id'] != '' )
1251 {
1252 $tax_query[] = array(
1253 'taxonomy' => 'availability',
1254 'terms' => explode(",", $atts['availability_id']),
1255 'compare' => 'IN',
1256 );
1257 }
1258
1259 if ( ! empty( $tax_query ) ) {
1260 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Property taxonomy filters are required by this shortcode; WordPress builds the query and the shortcode page limit is retained.
1261 $args['tax_query'] = $tax_query;
1262 }
1263
1264 if ( isset($atts['orderby']) && $atts['orderby'] == 'date' )
1265 {
1266 $args['orderby'] = 'meta_value';
1267 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
1268 $args['meta_key'] = '_on_market_change_date';
1269 }
1270
1271 $args['orderby'] .= ' post_title';
1272
1273 if ( ! empty( $atts['exclude'] ) )
1274 {
1275 $exclude_ids = array_map( 'absint', explode( ',', $atts['exclude'] ) );
1276 $exclude_ids = array_filter( $exclude_ids );
1277 if ( ! empty( $exclude_ids ) ) {
1278 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Explicit shortcode exclusions are validated as integer IDs above; retain this published selection feature within the shortcode page limit.
1279 $args['post__not_in'] = $exclude_ids;
1280 }
1281 }
1282
1283 ob_start();
1284
1285 if ( isset($atts['show_order']) && $atts['show_order'] != '' )
1286 {
1287 list( $args, $orderby ) = self::get_show_order_args( $atts, $args );
1288
1289 propertyhive_catalog_ordering( $atts['department'], $orderby );
1290 }
1291
1292 $properties = new WP_Query( apply_filters( 'propertyhive_shortcode_featured_properties_query', $args, $atts ) );
1293
1294 if ( isset($atts['show_result_count']) && $atts['show_result_count'] != '' )
1295 {
1296 $total_posts = $properties->found_posts;
1297
1298 $first = ( $atts['per_page'] * $paged ) - $atts['per_page'] + 1;
1299 $last = min( $total_posts, $atts['per_page'] * $paged );
1300
1301 propertyhive_result_count( $paged, $atts['per_page'], $total_posts, $first, $last);
1302 }
1303
1304 $propertyhive_loop['columns'] = (int)$atts['columns'];
1305
1306 if ( $properties->have_posts() ) : ?>
1307
1308 <?php
1309 ob_start();
1310 propertyhive_property_loop_start();
1311 $loop_start = ob_get_clean();
1312 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
1313 {
1314 $loop_start = str_replace("class=\"properties", "class=\"properties propertyhive-shortcode-carousel", $loop_start);
1315 }
1316 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Buffered loop template HTML; preserve theme overrides and the static carousel class insertion.
1317 echo $loop_start;
1318 ?>
1319
1320 <?php while ( $properties->have_posts() ) : $properties->the_post(); ?>
1321
1322 <?php ph_get_template_part( 'content', 'property-featured' ); ?>
1323
1324 <?php endwhile; // end of the loop. ?>
1325
1326 <?php propertyhive_property_loop_end(); ?>
1327
1328 <?php else: ?>
1329
1330 <p class="propertyhive-info no-results-message"><?php echo wp_kses_post($atts['no_results_output']); ?></p>
1331
1332 <?php endif;
1333
1334 if ( isset($atts['pagination']) && $atts['pagination'] != '' )
1335 {
1336 propertyhive_pagination( $properties->max_num_pages );
1337 }
1338
1339 wp_reset_postdata();
1340
1341 $shortcode_output = ob_get_clean();
1342
1343 return apply_filters( 'propertyhive_featured_properties_shortcode_output', '<div class="propertyhive propertyhive-featured-properties-shortcode columns-' . (int)$atts['columns'] . '">' . $shortcode_output . '</div>', $shortcode_output );
1344 }
1345
1346 /**
1347 * Output similar properties
1348 *
1349 * @param array $atts
1350 * @return string
1351 */
1352 public static function similar_properties( $atts ) {
1353
1354 global $property, $propertyhive_loop;
1355
1356 $atts = shortcode_atts( array(
1357 'per_page' => '2',
1358 'columns' => '2',
1359 'orderby' => 'rand',
1360 'order' => 'asc',
1361 'price_percentage_bounds' => 10,
1362 'bedroom_bounds' => 0,
1363 'matching_address_field' => '', // only return fields with matching address field. Options: address_two, address_three, address_four, location
1364 'property_id' => '',
1365 'availability_id' => '',
1366 'property_type_id' => '',
1367 'match_property_type' => '',
1368 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- These lines declare shortcode defaults; they do not pass a meta_key/exclude value to WP_Query or get_posts. 131 is the properties shortcode_atts default meta_key; 133/751/1045/1338 are exclude defaults in shortcode_atts.
1369 'exclude' => '',
1370 'no_results_output' => '',
1371 'carousel' => '',
1372 ), $atts, 'similar_properties' );
1373
1374 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
1375 {
1376 $params = array(
1377 'items' => 1,
1378 'controlsPosition' => 'bottom',
1379 'gutter' => 20,
1380 'mouseDrag' => true,
1381 'nav' => false,
1382 'navPosition' => 'bottom',
1383 'controlsText' => array("Prev", "Next"),
1384 'responsive' => array(
1385 640 => array(
1386 'items' => (int)$atts['columns']
1387 )
1388 )
1389 );
1390 $params = apply_filters( 'propertyhive_carousel_params', $params );
1391 $params = apply_filters( 'propertyhive_similar_properties_carousel_params', $params );
1392 wp_localize_script( 'propertyhive_carousel', 'propertyhive_carousel_params', $params );
1393
1394 wp_enqueue_style( 'tiny_slider_css' );
1395 wp_enqueue_script( 'tiny_slider' );
1396 wp_enqueue_script( 'propertyhive_carousel' );
1397 }
1398
1399 if ( $atts['property_id'] == '' && isset($property->id) )
1400 {
1401 $atts['property_id'] = $property->id;
1402 }
1403
1404 if ($atts['property_id'] != '')
1405 {
1406 $department = get_post_meta( $atts['property_id'], '_department', true );
1407
1408 $args = array(
1409 'post_type' => 'property',
1410 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Similar-property results must exclude the one current property; the result count is limited by per_page.
1411 'post__not_in' => array($atts['property_id']),
1412 'post_status' => ( ( is_user_logged_in() && current_user_can( 'manage_propertyhive' ) ) ? array('publish', 'private') : 'publish' ),
1413 'ignore_sticky_posts' => 1,
1414 'posts_per_page' => $atts['per_page'],
1415 'orderby' => $atts['orderby'],
1416 'order' => $atts['order'],
1417 'has_password' => false,
1418 );
1419
1420 $meta_query = array();
1421
1422 $meta_query[] = array(
1423 'key' => '_department',
1424 'value' => $department,
1425 );
1426
1427 $meta_query[] = array(
1428 'key' => '_on_market',
1429 'value' => 'yes',
1430 );
1431
1432 if ( $department != 'commercial' && ph_get_custom_department_based_on( $department ) != 'commercial' )
1433 {
1434 // residential
1435 $bedrooms = get_post_meta( $atts['property_id'], '_bedrooms', true );
1436 $lower_bedrooms = $bedrooms;
1437 $higher_bedrooms = $bedrooms;
1438 if ( !empty($bedrooms) && isset($atts['bedroom_bounds']) && $atts['bedroom_bounds'] != '' && is_numeric($atts['bedroom_bounds']) && $atts['bedroom_bounds'] > 0 )
1439 {
1440 $lower_bedrooms = $bedrooms - (int)$atts['bedroom_bounds'];
1441 $higher_bedrooms = $bedrooms + (int)$atts['bedroom_bounds'];
1442 }
1443
1444 if ( isset($atts['bedroom_bounds']) && is_numeric($atts['bedroom_bounds']) )
1445 {
1446 $meta_query[] = array(
1447 'key' => '_bedrooms',
1448 'value' => array( $lower_bedrooms, $higher_bedrooms ),
1449 'compare' => 'BETWEEN',
1450 'type' => 'NUMERIC'
1451 );
1452 }
1453
1454 $price = get_post_meta( $atts['property_id'], '_price_actual', true );
1455 $lower_price = $price;
1456 $higher_price = $price;
1457 $atts['price_percentage_bounds'] = str_replace("%", "", $atts['price_percentage_bounds']);
1458 if ( !empty($price) && isset($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] != '' && is_numeric($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] > 0 )
1459 {
1460 $lower_price = $price - ($price * (int)$atts['price_percentage_bounds'] / 100);
1461 $higher_price = $price + ($price * (int)$atts['price_percentage_bounds'] / 100);
1462 }
1463
1464 if ( isset($atts['price_percentage_bounds']) && is_numeric($atts['price_percentage_bounds']) )
1465 {
1466 $meta_query[] = array(
1467 'key' => '_price_actual',
1468 'value' => array( $lower_price, $higher_price ),
1469 'compare' => 'BETWEEN',
1470 'type' => 'NUMERIC'
1471 );
1472 }
1473 }
1474 else
1475 {
1476 // commercial
1477 $for_sale = get_post_meta( $atts['property_id'], '_for_sale', true );
1478 $to_rent = get_post_meta( $atts['property_id'], '_to_rent', true );
1479
1480 if ( $for_sale == 'yes' || $to_rent == 'yes' )
1481 {
1482 $sub_meta_query = array('relation' => 'OR');
1483
1484 if ( $for_sale == 'yes' )
1485 {
1486 $prices_sub_query = array('relation' => 'OR');
1487
1488 $price_from = get_post_meta( $atts['property_id'], '_price_from_actual', true );
1489 $price_to = get_post_meta( $atts['property_id'], '_price_to_actual', true );
1490
1491 if ( !empty($price_from) || !empty($price_to) )
1492 {
1493 if ( empty($price_from) )
1494 {
1495 $price_from = $price_to;
1496 }
1497 if ( empty($price_to) )
1498 {
1499 $price_to = $price_from;
1500 }
1501
1502 $lower_price_from = $price_from;
1503 $higher_price_from = $price_from;
1504 if ( isset($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] != '' && is_numeric($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] > 0 )
1505 {
1506 $lower_price_from = $price_from - ($price_from * (int)$atts['price_percentage_bounds'] / 100);
1507 $higher_price_from = $price_from + ($price_from * (int)$atts['price_percentage_bounds'] / 100);
1508 }
1509
1510 $lower_price_to = $price_to;
1511 $higher_price_to = $price_to;
1512 if ( isset($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] != '' && is_numeric($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] > 0 )
1513 {
1514 $lower_price_to = $price_to - ($price_to * (int)$atts['price_percentage_bounds'] / 100);
1515 $higher_price_to = $price_to + ($price_to * (int)$atts['price_percentage_bounds'] / 100);
1516 }
1517
1518 // where price from and price to not blank and price from -15%
1519 $price_sub_query = array();
1520
1521 $price_sub_query[] = array(
1522 'key' => '_price_from_actual',
1523 'value' => array( '', 0 ),
1524 'compare' => 'NOT IN'
1525 );
1526 $price_sub_query[] = array(
1527 'key' => '_price_to_actual',
1528 'value' => array( '', 0 ),
1529 'compare' => 'NOT IN'
1530 );
1531 $price_sub_query[] = array(
1532 'key' => '_price_to_actual',
1533 'value' => $lower_price_from,
1534 'compare' => '>=',
1535 'type' => 'NUMERIC'
1536 );
1537 $price_sub_query[] = array(
1538 'key' => '_price_from_actual',
1539 'value' => $higher_price_to,
1540 'compare' => '<=',
1541 'type' => 'NUMERIC'
1542 );
1543
1544 $prices_sub_query[] = $price_sub_query;
1545 }
1546
1547 $sub_meta_query[] = array(
1548 array(
1549 'key' => '_for_sale',
1550 'value' => $for_sale,
1551 ),
1552 $prices_sub_query
1553 );
1554 }
1555 elseif ( $to_rent == 'yes' )
1556 {
1557 $prices_sub_query = array('relation' => 'OR');
1558
1559 $price_from = get_post_meta( $atts['property_id'], '_rent_from_actual', true );
1560 $price_to = get_post_meta( $atts['property_id'], '_rent_to_actual', true );
1561
1562 if ( !empty($price_from) || !empty($price_to) )
1563 {
1564 if ( empty($price_from) )
1565 {
1566 $price_from = $price_to;
1567 }
1568 if ( empty($price_to) )
1569 {
1570 $price_to = $price_from;
1571 }
1572
1573 $lower_price_from = $price_from;
1574 $higher_price_from = $price_from;
1575 if ( isset($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] != '' && is_numeric($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] > 0 )
1576 {
1577 $lower_price_from = $price_from - ($price_from * (int)$atts['price_percentage_bounds'] / 100);
1578 $higher_price_from = $price_from + ($price_from * (int)$atts['price_percentage_bounds'] / 100);
1579 }
1580
1581 $lower_price_to = $price_to;
1582 $higher_price_to = $price_to;
1583 if ( isset($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] != '' && is_numeric($atts['price_percentage_bounds']) && $atts['price_percentage_bounds'] > 0 )
1584 {
1585 $lower_price_to = $price_to - ($price_to * (int)$atts['price_percentage_bounds'] / 100);
1586 $higher_price_to = $price_to + ($price_to * (int)$atts['price_percentage_bounds'] / 100);
1587 }
1588
1589 // where price from and price to not blank and price from -15%
1590 $price_sub_query = array();
1591
1592 $price_sub_query[] = array(
1593 'key' => '_rent_from_actual',
1594 'value' => array( '', 0 ),
1595 'compare' => 'NOT IN'
1596 );
1597 $price_sub_query[] = array(
1598 'key' => '_rent_to_actual',
1599 'value' => array( '', 0 ),
1600 'compare' => 'NOT IN'
1601 );
1602 $price_sub_query[] = array(
1603 'key' => '_rent_to_actual',
1604 'value' => $lower_price_from,
1605 'compare' => '>=',
1606 'type' => 'NUMERIC'
1607 );
1608 $price_sub_query[] = array(
1609 'key' => '_rent_from_actual',
1610 'value' => $higher_price_to,
1611 'compare' => '<=',
1612 'type' => 'NUMERIC'
1613 );
1614
1615 $prices_sub_query[] = $price_sub_query;
1616 }
1617
1618 $sub_meta_query[] = array(
1619 array(
1620 'key' => '_to_rent',
1621 'value' => $to_rent,
1622 ),
1623 $prices_sub_query
1624 );
1625 }
1626
1627 $meta_query[] = $sub_meta_query;
1628 }
1629 }
1630
1631 if ( isset($atts['matching_address_field']) && in_array($atts['matching_address_field'], array( 'address_two', 'address_three', 'address_four' )) )
1632 {
1633 $address_field = get_post_meta( $atts['property_id'], '_' . $atts['matching_address_field'], true );
1634
1635 $meta_query[] = array(
1636 'key' => '_' . $atts['matching_address_field'],
1637 'value' => $address_field,
1638 );
1639 }
1640
1641 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Property eligibility and matching fields live in the established metadata schema; retain these filters and the shortcode page limit.
1642 $args['meta_query'] = $meta_query;
1643
1644 $tax_query = array();
1645
1646 if ( isset($atts['availability_id']) && $atts['availability_id'] != '' )
1647 {
1648 $tax_query[] = array(
1649 'taxonomy' => 'availability',
1650 'terms' => explode(",", $atts['availability_id']),
1651 'compare' => 'IN',
1652 );
1653 }
1654
1655 $property_types = array();
1656 if ( isset($atts['property_type_id']) && $atts['property_type_id'] != '' )
1657 {
1658 $property_types = explode(",", $atts['property_type_id']);
1659 }
1660
1661 if ( isset($atts['match_property_type']) && $atts['match_property_type'] != '' )
1662 {
1663 $term_list = wp_get_post_terms((int)$atts['property_id'], ( ( $department == 'commercial' || ph_get_custom_department_based_on( $department ) == 'commercial' ) ? 'commercial_' : '' ) . 'property_type', array("fields" => "ids"));
1664
1665 if ( !is_wp_error($term_list) && is_array($term_list) && !empty($term_list) )
1666 {
1667 $property_types = $term_list;
1668 }
1669 }
1670
1671 if ( !empty($property_types) )
1672 {
1673 $property_types = array_unique($property_types);
1674 $property_types = array_filter($property_types);
1675
1676 if ( $department != 'commercial' && ph_get_custom_department_based_on( $department ) != 'commercial' )
1677 {
1678 $tax_query[] = array(
1679 'taxonomy' => 'property_type',
1680 'terms' => $property_types,
1681 'compare' => 'IN',
1682 );
1683 }
1684 else
1685 {
1686 $tax_query[] = array(
1687 'taxonomy' => 'commercial_property_type',
1688 'terms' => $property_types,
1689 'compare' => 'IN',
1690 );
1691 }
1692 }
1693
1694 if ( isset($atts['matching_address_field']) && $atts['matching_address_field'] == 'location' )
1695 {
1696 $term_list = wp_get_post_terms($atts['property_id'], 'location', array("fields" => "ids"));
1697
1698 if ( !is_wp_error($term_list) && is_array($term_list) && !empty($term_list) )
1699 {
1700 $tax_query[] = array(
1701 'taxonomy' => 'location',
1702 'terms' => $term_list,
1703 'compare' => 'IN',
1704 );
1705 }
1706 }
1707
1708 if ( ! empty( $tax_query ) ) {
1709 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Property taxonomy filters are required by this shortcode; WordPress builds the query and the shortcode page limit is retained.
1710 $args['tax_query'] = $tax_query;
1711 }
1712
1713 if ( isset($atts['orderby']) && $atts['orderby'] == 'date' )
1714 {
1715 $args['orderby'] = 'meta_value';
1716 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
1717 $args['meta_key'] = '_on_market_change_date';
1718 }
1719
1720 $args['orderby'] .= ' post_title';
1721
1722 if ( ! empty( $atts['exclude'] ) )
1723 {
1724 $exclude_ids = array_map( 'absint', explode( ',', $atts['exclude'] ) );
1725 $exclude_ids = array_filter( $exclude_ids );
1726 if ( ! empty( $exclude_ids ) ) {
1727 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Explicit shortcode exclusions are validated as integer IDs above; retain this published selection feature within the shortcode page limit.
1728 $args['post__not_in'] = $exclude_ids;
1729 }
1730 }
1731
1732 ob_start();
1733
1734 $properties = new WP_Query( apply_filters( 'propertyhive_shortcode_similar_properties_query', $args, $atts ) );
1735
1736 $propertyhive_loop['columns'] = (int)$atts['columns'];
1737
1738 if ( $properties->have_posts() ) : ?>
1739
1740 <?php
1741 ob_start();
1742 propertyhive_property_loop_start();
1743 $loop_start = ob_get_clean();
1744 if ( isset($atts['carousel']) && !empty($atts['carousel']) )
1745 {
1746 $loop_start = str_replace("class=\"properties", "class=\"properties propertyhive-shortcode-carousel", $loop_start);
1747 }
1748 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Buffered loop template HTML; preserve theme overrides and the static carousel class insertion.
1749 echo $loop_start;
1750 ?>
1751
1752 <?php while ( $properties->have_posts() ) : $properties->the_post(); ?>
1753
1754 <?php ph_get_template_part( 'content', 'property-featured' ); ?>
1755
1756 <?php endwhile; // end of the loop. ?>
1757
1758 <?php propertyhive_property_loop_end(); ?>
1759
1760 <?php else: ?>
1761
1762 <p class="propertyhive-info no-results-message"><?php echo wp_kses_post($atts['no_results_output']); ?></p>
1763
1764 <?php endif;
1765
1766 wp_reset_postdata();
1767 }
1768 else
1769 {
1770 echo 'No property_id passed into similar_properties shortcode';
1771 }
1772
1773 $shortcode_output = ob_get_clean();
1774
1775 return apply_filters( 'propertyhive_similar_properties_shortcode_output', '<div class="propertyhive propertyhive-similar-properties-shortcode columns-' . (int)$atts['columns'] . '">' . $shortcode_output . '</div>', $shortcode_output );
1776 }
1777
1778 /**
1779 * Output property map
1780 * Should only be used on a property page or where the $property var is set
1781 *
1782 * @param array $atts
1783 * @return string
1784 */
1785 public static function property_map( $atts ) {
1786
1787 global $property;
1788
1789 $atts = shortcode_atts( array(
1790 'id' => '',
1791 'height' => '400',
1792 'zoom' => '14',
1793 'scrollwheel' => 'true',
1794 'init_on_load' => 'true',
1795 'embed' => 'false'
1796 ), $atts, 'property_map' );
1797
1798 ob_start();
1799
1800 get_property_map( $atts );
1801
1802 return ob_get_clean();
1803 }
1804
1805 /**
1806 * Output static (image) property map
1807 * Should only be used on a property page or where the $property var is set
1808 *
1809 * @param array $atts
1810 * @return string
1811 */
1812 public static function property_static_map( $atts ) {
1813
1814 global $property;
1815
1816 $atts = shortcode_atts( array(
1817 'id' => '',
1818 'height' => '400',
1819 'zoom' => '14',
1820 'link' => 'true',
1821 ), $atts, 'property_static_map' );
1822
1823 ob_start();
1824
1825 get_property_static_map( $atts );
1826
1827 return ob_get_clean();
1828 }
1829
1830 /**
1831 * Output property street view
1832 * Should only be used on a property page or where the $property var is set
1833 *
1834 * @param array $atts
1835 * @return string
1836 */
1837 public static function property_street_view( $atts ) {
1838
1839 global $property;
1840
1841 $atts = shortcode_atts( array(
1842 'height' => '400',
1843 'init_on_load' => 'true',
1844 'embed' => 'false'
1845 ), $atts, 'property_street_view' );
1846
1847 ob_start();
1848
1849 get_property_street_view( $atts );
1850
1851 return ob_get_clean();
1852 }
1853
1854 /**
1855 * Output property office details
1856 * Should only be used on a property page or where the $property var is set
1857 *
1858 * @param array $atts
1859 * @return string
1860 */
1861 public static function property_office_details( $atts ) {
1862
1863 global $property;
1864
1865 $atts = shortcode_atts( array(
1866 'address_separator' => '<br>',
1867 'hyperlink_telephone_number' => true,
1868 'hyperlink_email_address' => true,
1869 ), $atts, 'property_office_details' );
1870
1871 $atts['hyperlink_telephone_number'] = (($atts['hyperlink_telephone_number'] === 'true' || $atts['hyperlink_telephone_number'] === true) ? true : false);
1872 $atts['hyperlink_email_address'] = (($atts['hyperlink_email_address'] === 'true' || $atts['hyperlink_email_address'] === true) ? true : false);
1873
1874 ob_start();
1875
1876 if ( $property->office_id != '' )
1877 {
1878 echo '<div class="property-office-details">';
1879
1880 if ( $property->office_name != '' )
1881 {
1882 echo '<div class="office-name">' . esc_html($property->office_name) . '</div>';
1883 }
1884
1885 if ( $property->get_office_address( $atts['address_separator'] ) != '' )
1886 {
1887 echo '<div class="office-address">' . wp_kses_post( $property->get_office_address( $atts['address_separator'] ) ) . '</div>';
1888 }
1889
1890 if ( $property->office_telephone_number != '' )
1891 {
1892 echo '<div class="office-telephone-number">' . ( ($atts['hyperlink_telephone_number'] === true) ? '<a href="tel:' . esc_attr($property->office_telephone_number) . '">' : '' ) . esc_html($property->office_telephone_number) . ( ($atts['hyperlink_telephone_number'] === true) ? '</a>' : '' ) . '</div>';
1893 }
1894
1895 if ( $property->office_email_address != '' )
1896 {
1897 echo '<div class="office-email-address">' . ( ($atts['hyperlink_email_address'] === true) ? '<a href="mailto:' . esc_attr($property->office_email_address) . '">' : '' ) . esc_html($property->office_email_address) . ( ($atts['hyperlink_email_address'] === true) ? '</a>' : '' ) . '</div>';
1898 }
1899
1900 echo '</div>';
1901 }
1902
1903 return ob_get_clean();
1904 }
1905
1906 /**
1907 * Output office map
1908 *
1909 * @param array $atts
1910 * @return string
1911 */
1912 public static function office_map( $atts ) {
1913
1914 $offices_with_lat_lng = 0;
1915 if ( !isset($atts['office_id']) || ( isset($atts['office_id']) && $atts['office_id'] == '' ) )
1916 {
1917 $args = array(
1918 'post_type' => 'office',
1919 'nopaging' => true
1920 );
1921
1922 $office_query = new WP_Query( $args );
1923
1924 if ( $office_query->have_posts() )
1925 {
1926 while ( $office_query->have_posts() )
1927 {
1928 $office_query->the_post();
1929
1930 $lat = get_post_meta(get_the_ID(), '_office_latitude', TRUE);
1931 $lng = get_post_meta(get_the_ID(), '_office_longitude', TRUE);
1932
1933 if ( $lat != '' && $lng != '' )
1934 {
1935 ++$offices_with_lat_lng;
1936 }
1937 }
1938 }
1939 wp_reset_postdata();
1940 }
1941
1942 $atts = shortcode_atts( array(
1943 'office_id' => '', // if wanting to show map for a particular office
1944 'height' => '400',
1945 'zoom' => ( ( ( !isset($atts['office_id']) || ( isset($atts['office_id']) && $atts['office_id'] == '' ) ) && !isset($atts['zoom']) && $offices_with_lat_lng > 1 ) ? 'auto' : '14' ),
1946 'scrollwheel' => 'true'
1947 ), $atts, 'office_map' );
1948
1949 ob_start();
1950
1951 $api_key = get_option('propertyhive_google_maps_api_key', '');
1952 wp_register_script('googlemaps', '//maps.googleapis.com/maps/api/js?' . ( ( $api_key != '' && $api_key !== FALSE ) ? 'key=' . $api_key : '' ), false, '3', true );
1953 wp_enqueue_script('googlemaps');
1954
1955 echo '<div id="office_map_canvas" style="height:' . (int) ( ( isset($atts['height']) && !empty($atts['height']) && is_numeric($atts['height']) ) ? $atts['height'] : 400 ) . 'px"></div>';
1956 ?>
1957 <script>
1958
1959 // We declare vars globally so developers can access them
1960 var office_map; // Global declaration of the map
1961 var office_marker;
1962 var ph_office_map_lat_lngs = new Array();
1963
1964 function initialize_office_map() {
1965
1966 <?php
1967 $args = array(
1968 'post_type' => 'office',
1969 'posts_per_page' => 1
1970 );
1971
1972 if ( isset($atts['office_id']) && (int)$atts['office_id'] != 0 )
1973 {
1974 $args['p'] = (int)$atts['office_id'];
1975 }
1976 else
1977 {
1978 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Selects the primary office from the established metadata schema and returns at most one office.
1979 $args['meta_query'] = array(
1980 array(
1981 'key' => 'primary',
1982 'value' => '1'
1983 )
1984 );
1985 }
1986
1987 $office_query = new WP_Query( $args );
1988
1989 $lat = '';
1990 $lng = '';
1991
1992 if ( $office_query->have_posts() )
1993 {
1994 while ( $office_query->have_posts() )
1995 {
1996 $office_query->the_post();
1997
1998 $lat = get_post_meta(get_the_ID(), '_office_latitude', TRUE);
1999 $lng = get_post_meta(get_the_ID(), '_office_longitude', TRUE);
2000 }
2001 }
2002 if ( $lat == '' || $lng == '' )
2003 {
2004 $lat = '51.509865';
2005 $lng = '-0.118092';
2006 }
2007 ?>
2008 var myLatlng = new google.maps.LatLng(<?php echo (float)$lat; ?>, <?php echo (float)$lng; ?>);
2009 var map_options = {
2010 zoom: <?php echo ( ( isset($atts['zoom']) && !empty($atts['zoom']) && is_numeric($atts['zoom']) && $atts['zoom'] != 'auto' ) ? (int)$atts['zoom'] : '14' ); ?>,
2011 center: myLatlng,
2012 mapTypeId: google.maps.MapTypeId.ROADMAP,
2013 scrollwheel: <?php echo ( ( isset($atts['scrollwheel']) && ($atts['scrollwheel'] === 'false' || $atts['scrollwheel'] === FALSE) ) ? 'false' : 'true' ); ?>
2014 }
2015 <?php
2016 if ( class_exists( 'PH_Map_Search' ) )
2017 {
2018 $map_add_on_settings = get_option( 'propertyhive_map_search', array() );
2019
2020 if ( isset($map_add_on_settings['style_js']) && is_string($map_add_on_settings['style_js']) && trim($map_add_on_settings['style_js']) != '' )
2021 {
2022 // Google Maps styles are JSON arrays, including legacy Snazzy Maps style properties.
2023 $map_styles = json_decode( $map_add_on_settings['style_js'] );
2024 if ( is_array( $map_styles ) ) {
2025 echo 'map_options.styles = ' . wp_json_encode( $map_styles, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';';
2026 }
2027 }
2028 }
2029
2030 do_action( 'propertyhive_office_map_options' );
2031 ?>
2032 office_map = new google.maps.Map(document.getElementById("office_map_canvas"), map_options);
2033
2034 <?php
2035 $args = array(
2036 'post_type' => 'office',
2037 'nopaging' => true
2038 );
2039
2040 if ( isset($atts['office_id']) && (int)$atts['office_id'] != 0 )
2041 {
2042 $args['p'] = (int)$atts['office_id'];
2043 }
2044
2045 $office_query = new WP_Query( $args );
2046
2047 if ( $office_query->have_posts() )
2048 {
2049 while ( $office_query->have_posts() )
2050 {
2051 $office_query->the_post();
2052
2053 $lat = get_post_meta(get_the_ID(), '_office_latitude', TRUE);
2054 $lng = get_post_meta(get_the_ID(), '_office_longitude', TRUE);
2055
2056 if ( $lat != '' && $lng != '' )
2057 {
2058 ?>
2059 var myLatlng = new google.maps.LatLng(<?php echo (float)$lat; ?>, <?php echo (float)$lng; ?>);
2060
2061 var marker_options = {
2062 map: office_map,
2063 position: myLatlng,
2064 title: "<?php echo esc_attr(get_the_title()); ?>"
2065 };
2066
2067 <?php
2068 if ( class_exists( 'PH_Map_Search' ) )
2069 {
2070 $map_add_on_settings = get_option( 'propertyhive_map_search', array() );
2071
2072 if ( isset($map_add_on_settings['icon_type']) && $map_add_on_settings['icon_type'] == 'custom_single' && isset($map_add_on_settings['custom_icon_attachment_id']) && $map_add_on_settings['custom_icon_attachment_id'] != '' )
2073 {
2074 $marker_icon_url = wp_get_attachment_url( $map_add_on_settings['custom_icon_attachment_id'] );
2075 if ( $marker_icon_url !== FALSE )
2076 {
2077 echo 'marker_options.icon = \'' . esc_url($marker_icon_url) . '\';';
2078 }
2079 }
2080 }
2081 ?>
2082
2083 <?php do_action( 'propertyhive_office_map_marker_options' ); ?>
2084
2085 office_marker = new google.maps.Marker(marker_options);
2086 ph_office_map_lat_lngs.push(office_marker.getPosition());
2087 <?php
2088 }
2089 }
2090 }
2091 wp_reset_postdata();
2092
2093 if ( $atts['zoom'] == 'auto' ) { echo 'ph_fit_office_map_to_bounds();'; }
2094 ?>
2095 }
2096
2097 function ph_fit_office_map_to_bounds()
2098 {
2099 var bounds = new google.maps.LatLngBounds();
2100 if ( ph_office_map_lat_lngs.length > 0 )
2101 {
2102 for ( var i = 0; i < ph_office_map_lat_lngs.length; i++ )
2103 {
2104 bounds.extend(ph_office_map_lat_lngs[i]);
2105 }
2106 office_map.fitBounds(bounds);
2107 }
2108 }
2109
2110 if(window.addEventListener) {
2111 window.addEventListener('load', initialize_office_map);
2112 }else{
2113 window.attachEvent('onload', initialize_office_map);
2114 }
2115
2116 </script>
2117 <?php
2118 return ob_get_clean();
2119 }
2120
2121 /**
2122 * Output applicant registration form
2123 *
2124 * @param array $atts
2125 * @return string
2126 */
2127 public static function applicant_registration_form( $atts ) {
2128
2129 $atts = shortcode_atts( array(
2130
2131 ), $atts, 'applicant_registration_form' );
2132
2133 $assets_path = str_replace( array( 'http:', 'https:' ), '', PH()->plugin_url() ) . '/assets/';
2134 wp_enqueue_script( 'propertyhive_account', $assets_path . 'js/frontend/account.js', array( 'jquery' ), PH_VERSION, true );
2135
2136 ob_start();
2137
2138 if ( is_user_logged_in() )
2139 {
2140 ph_get_template( 'account/already-logged-in.php' );
2141 return ob_get_clean();
2142 }
2143
2144 $form_controls = ph_get_user_details_form_fields();
2145
2146 $form_controls = apply_filters( 'propertyhive_user_details_form_fields', $form_controls );
2147
2148 $form_controls_2 = ph_get_applicant_requirements_form_fields();
2149
2150 $form_controls_2 = apply_filters( 'propertyhive_applicant_requirements_form_fields', $form_controls_2, false );
2151
2152 $form_controls = array_merge( $form_controls, $form_controls_2 );
2153
2154 if ( get_option( 'propertyhive_applicant_registration_form_disclaimer', '' ) != '' )
2155 {
2156 $disclaimer = get_option( 'propertyhive_applicant_registration_form_disclaimer', '' );
2157
2158 $form_controls['disclaimer'] = array(
2159 'type' => 'checkbox',
2160 'label' => $disclaimer,
2161 'label_style' => 'width:100%;',
2162 'required' => true
2163 );
2164 }
2165
2166 $form_controls = apply_filters( 'propertyhive_applicant_registration_form_fields', $form_controls );
2167
2168 ph_get_template( 'account/applicant-registration-form.php', array( 'form_controls' => $form_controls ) );
2169
2170 return ob_get_clean();
2171 }
2172
2173 /**
2174 * Output 'Login' page
2175 *
2176 * @param array $atts
2177 * @return string
2178 */
2179 public static function login_form( $atts )
2180 {
2181 $atts = shortcode_atts( array(
2182
2183 ), $atts, 'login_form' );
2184
2185 $assets_path = str_replace( array( 'http:', 'https:' ), '', PH()->plugin_url() ) . '/assets/';
2186 wp_enqueue_script( 'propertyhive_account', $assets_path . 'js/frontend/account.js', array( 'jquery' ), PH_VERSION, true );
2187
2188 ob_start();
2189
2190 if ( is_user_logged_in() )
2191 {
2192 ph_get_template( 'account/already-logged-in.php' );
2193 return ob_get_clean();
2194 }
2195
2196 // Check 'propertyhive_applicant_users' setting is enabled
2197 if ( get_option( 'propertyhive_applicant_users', '' ) != 'yes' )
2198 {
2199 ph_get_template( 'account/invalid-access.php' );
2200 return ob_get_clean();
2201 }
2202
2203 ph_get_template( 'account/login-form.php' );
2204
2205 return ob_get_clean();
2206
2207 }
2208
2209 /**
2210 * Output 'Reset Password' page
2211 *
2212 * @param array $atts
2213 * @return string
2214 */
2215 public static function reset_password_form( $atts )
2216 {
2217 $atts = shortcode_atts( array(
2218
2219 ), $atts, 'reset_password_form' );
2220
2221 $assets_path = str_replace( array( 'http:', 'https:' ), '', PH()->plugin_url() ) . '/assets/';
2222 wp_enqueue_script( 'propertyhive_account', $assets_path . 'js/frontend/account.js', array( 'jquery' ), PH_VERSION, true );
2223
2224 ob_start();
2225
2226 if ( is_user_logged_in() )
2227 {
2228 ph_get_template( 'account/already-logged-in.php' );
2229 return ob_get_clean();
2230 }
2231
2232 // Check 'propertyhive_applicant_users' setting is enabled
2233 if ( get_option( 'propertyhive_applicant_users', '' ) != 'yes' )
2234 {
2235 ph_get_template( 'account/invalid-access.php' );
2236 return ob_get_clean();
2237 }
2238
2239 // Display only: WordPress validates the opaque reset key; the reset action has its own nonce.
2240 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- An opaque key must remain intact for check_password_reset_key().
2241 $key = isset( $_GET['key'] ) && is_string( $_GET['key'] ) ? wp_unslash( $_GET['key'] ) : '';
2242 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only selects the user whose reset key WordPress verifies below.
2243 $user_id = isset( $_GET['id'] ) && is_string( $_GET['id'] ) ? absint( $_GET['id'] ) : 0;
2244 if ( $key === '' || $user_id === 0 )
2245 {
2246 echo esc_html(__( 'Invalid key or id provided. Please try again', 'propertyhive' ));
2247 return ob_get_clean();
2248 }
2249
2250
2251 $userdata = get_userdata( $user_id );
2252 $user_login = $userdata ? $userdata->user_login : '';
2253
2254 $user = check_password_reset_key( $key, $user_login );
2255
2256 if ( is_wp_error( $user ) )
2257 {
2258 echo esc_html(__( 'This key is invalid or has already been used. Please reset your password again if needed.', 'propertyhive' ));
2259 return ob_get_clean();
2260 }
2261
2262 ph_get_template( 'account/reset-password-form.php', array( 'reset_key' => $key, 'reset_login' => $user_login ) );
2263
2264 return ob_get_clean();
2265
2266 }
2267
2268 /**
2269 * Output 'My Account' page
2270 *
2271 * @param array $atts
2272 * @return string
2273 */
2274 public static function my_account( $atts )
2275 {
2276 $atts = shortcode_atts( array(
2277
2278 ), $atts, 'my_account' );
2279
2280 $assets_path = str_replace( array( 'http:', 'https:' ), '', PH()->plugin_url() ) . '/assets/';
2281 wp_enqueue_script( 'propertyhive_account', $assets_path . 'js/frontend/account.js', array( 'jquery' ), PH_VERSION, true );
2282
2283 ob_start();
2284
2285 // Check user is logged in
2286 if ( !is_user_logged_in() )
2287 {
2288 ph_get_template( 'account/invalid-access.php' );
2289 return ob_get_clean();
2290 }
2291
2292 // Check 'propertyhive_applicant_users' setting is enabled
2293 if ( get_option( 'propertyhive_applicant_users', '' ) != 'yes' )
2294 {
2295 ph_get_template( 'account/invalid-access.php' );
2296 return ob_get_clean();
2297 }
2298
2299 ph_get_template( 'account/my-account.php' );
2300
2301 return ob_get_clean();
2302
2303 }
2304
2305 private static function get_show_order_args( $atts, $args )
2306 {
2307 $orderby = '';
2308
2309 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only sort selection; the query helper validates the ordering value.
2310 if ( isset( $_GET['orderby'] ) && is_string( $_GET['orderby'] ) && $_GET['orderby'] != '' )
2311 {
2312 $PH_Query = new PH_Query();
2313 $ordering_args = $PH_Query->get_search_results_ordering_args();
2314
2315 $args['orderby'] = $ordering_args['orderby'];
2316 $args['order'] = $ordering_args['order'];
2317
2318 if ( isset( $ordering_args['meta_key'] ) )
2319 {
2320 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Property sorting uses the established price, floor-area or market-date metadata; retain the selected ordering and existing query limits.
2321 $args['meta_key'] = $ordering_args['meta_key'];
2322 }
2323 else
2324 {
2325 unset($args['meta_key']);
2326 }
2327 }
2328 else
2329 {
2330 switch ( $atts['orderby'] )
2331 {
2332 case 'date':
2333 $orderby = 'date';
2334 break;
2335 case 'meta_value_num':
2336
2337 switch ( $atts['meta_key'] )
2338 {
2339 case '_price_actual':
2340 $orderby = 'price';
2341 break;
2342 case '_floor_area_from_sqft':
2343 $orderby = 'floor_area';
2344 break;
2345 }
2346
2347 if ( $orderby != '' && !empty($atts['order']) )
2348 {
2349 $orderby .= '-' . $atts['order'];
2350 }
2351 break;
2352 }
2353 }
2354
2355 return array( $args, $orderby );
2356 }
2357 }
2358