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-form-functions.php +789 -241 1.4.462.3.1 View file →
@@ -1,5 +1,13 @@
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 +
5 +
6 +if ( ! defined( 'ABSPATH' ) ) {
7 + exit;
8 +}
9 +
2 10 /**
3 11 * PropertyHive Form Functions
4 12 *
5 13 * Functions related to drawing forms on the frontend.
@@ -16,8 +24,9 @@
16 24 *
17 25 * @param string $id
18 26 * @return void
19 27 */
28 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_get_search_form; the established callable name is part of the plugin/extension API and must remain stable.
20 29 function ph_get_search_form( $id = 'default' ) {
21 30
22 31 $form_controls = ph_get_search_form_fields();
23 32
@@ -34,8 +43,9 @@
34 43 $form_controls['department'] = $original_department;
35 44 }
36 45
37 46 // append hidden order and view fields so these are maintained should a new search be performed
47 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
38 48 foreach ( $_REQUEST as $key => $value )
39 49 {
40 50 if ( isset($form_controls[$key]) )
41 51 continue;
@@ -42,9 +52,18 @@
42 52
43 53 if ( $key == 'officeID' && isset($form_controls['office']) )
44 54 continue;
45 55
56 + if ( $key == 'paged' )
57 + continue;
46 58
59 + if (
60 + ( $key == 'minimum_price' || $key == 'maximum_price' ) && array_key_exists('price_slider', $form_controls) ||
61 + ( $key == 'minimum_rent' || $key == 'maximum_rent' ) && array_key_exists('rent_slider', $form_controls) ||
62 + ( $key == 'minimum_bedrooms' || $key == 'maximum_bedrooms' ) && array_key_exists('bedrooms_slider', $form_controls)
63 + )
64 + continue;
65 +
47 66 // we've received a field that isn't a standard form control so let's store it in a hidden field so it's not lost
48 67 if ( is_array($value) )
49 68 {
50 69 foreach ( $value as $i => $val )
@@ -57,8 +76,11 @@
57 76 $form_controls[$key] = array('type' => 'hidden', 'value' => stripslashes( ph_clean( $value) ));
58 77 }
59 78 }
60 79
80 + $form_controls = apply_filters( 'propertyhive_search_form_fields_after_' . $id, $form_controls );
81 + $form_controls = apply_filters( 'propertyhive_search_form_fields_after', $form_controls );
82 +
61 83 ph_get_template( 'global/search-form.php', array( 'form_controls' => $form_controls, 'id' => $id ) );
62 84
63 85 }
64 86
@@ -66,48 +88,100 @@
66 88 * Get default fields to be shown on search forms
67 89 *
68 90 * @return array
69 91 */
92 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_get_search_form_fields; the established callable name is part of the plugin/extension API and must remain stable.
70 93 function ph_get_search_form_fields()
71 94 {
72 95 $fields = array();
73 96
74 - $departments = array();
75 - $value = '';
76 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' )
97 + $departments = ph_get_departments();
98 +
99 + $department_options = array();
100 + $default_value = '';
101 +
102 + foreach ( $departments as $key => $value )
77 103 {
78 - $departments['residential-sales'] = __( 'Sales', 'propertyhive' );
79 - if ($value == '' && (get_option( 'propertyhive_primary_department' ) == 'residential-sales' || get_option( 'propertyhive_primary_department' ) === FALSE) )
104 + if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
80 105 {
81 - $value = 'residential-sales';
106 + $department_options[$key] = $value;
107 +
108 + if ($default_value == '' && get_option( 'propertyhive_primary_department' ) == $key )
109 + {
110 + $default_value = $key;
111 + }
82 112 }
83 113 }
84 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
114 +
115 + $sales_department_active = false;
116 + if ( array_key_exists('residential-sales', $departments) )
85 117 {
86 - $departments['residential-lettings'] = __( 'Lettings', 'propertyhive' );
87 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
118 + $sales_department_active = true;
119 + }
120 + else
121 + {
122 + $custom_departments = ph_get_custom_departments();
123 + if ( !empty($custom_departments) )
88 124 {
89 - $value = 'residential-lettings';
125 + foreach ( $custom_departments as $key => $department )
126 + {
127 + if ( isset($department['based_on']) && $department['based_on'] == 'residential-sales' )
128 + {
129 + $sales_department_active = true;
130 + }
131 + }
90 132 }
91 133 }
92 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
134 +
135 + $lettings_department_active = false;
136 + if ( array_key_exists('residential-lettings', $departments) )
93 137 {
94 - $departments['commercial'] = __( 'Commercial', 'propertyhive' );
95 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'commercial')
138 + $lettings_department_active = true;
139 + }
140 + else
141 + {
142 + $custom_departments = ph_get_custom_departments();
143 + if ( !empty($custom_departments) )
96 144 {
97 - $value = 'commercial';
145 + foreach ( $custom_departments as $key => $department )
146 + {
147 + if ( isset($department['based_on']) && $department['based_on'] == 'residential-lettings' )
148 + {
149 + $lettings_department_active = true;
150 + }
151 + }
98 152 }
99 153 }
100 154
155 + $commercial_department_active = false;
156 + if ( array_key_exists('commercial', $departments) )
157 + {
158 + $commercial_department_active = true;
159 + }
160 + else
161 + {
162 + $custom_departments = ph_get_custom_departments();
163 + if ( !empty($custom_departments) )
164 + {
165 + foreach ( $custom_departments as $key => $department )
166 + {
167 + if ( isset($department['based_on']) && $department['based_on'] == 'commercial' )
168 + {
169 + $commercial_department_active = true;
170 + }
171 + }
172 + }
173 + }
174 +
101 175 $fields['department'] = array(
102 176 'type' => 'radio',
103 - 'options' => $departments,
104 - 'value' => $value
177 + 'options' => $department_options,
178 + 'value' => $default_value
105 179 );
106 180
107 - if ( array_key_exists('residential-sales', $departments) || array_key_exists('residential-lettings', $departments) )
181 + if ( $sales_department_active || $lettings_department_active )
108 182 {
109 - if ( array_key_exists('residential-sales', $departments) )
183 + if ( $sales_department_active )
110 184 {
111 185 $prices = array(
112 186 '' => __( 'No preference', 'propertyhive' ),
113 187 '100000' => '&pound;100,000',
@@ -136,9 +210,9 @@
136 210 'options' => $prices
137 211 );
138 212 }
139 213
140 - if ( array_key_exists('residential-lettings', $departments) )
214 + if ( $lettings_department_active )
141 215 {
142 216 $prices = array(
143 217 '' => __( 'No preference', 'propertyhive' ),
144 218 '500' => '&pound;500 PCM',
@@ -182,9 +256,9 @@
182 256 'label' => __( 'Type', 'propertyhive' ),
183 257 );
184 258 }
185 259
186 - if ( array_key_exists('commercial', $departments) )
260 + if ( $commercial_department_active )
187 261 {
188 262 $sizes = array(
189 263 '' => __( 'No preference', 'propertyhive' ),
190 264 '250' => '250 sq ft',
@@ -212,45 +286,13 @@
212 286 'before' => '<div class="control control-maximum_floor_area commercial-only">',
213 287 'options' => $sizes
214 288 );
215 289
216 - // Property Type
217 - $options = array( '' => __( 'No preference', 'propertyhive' ) );
218 - $args = array(
219 - 'hide_empty' => false,
220 - 'parent' => 0
221 - );
222 - $terms = get_terms( 'commercial_property_type', $args );
223 -
224 - $selected_value = '';
225 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
226 - {
227 - foreach ($terms as $term)
228 - {
229 - $options[$term->term_id] = $term->name;
230 -
231 - $args = array(
232 - 'hide_empty' => false,
233 - 'parent' => $term->term_id
234 - );
235 - $subterms = get_terms( 'commercial_property_type', $args );
236 -
237 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
238 - {
239 - foreach ($subterms as $term)
240 - {
241 - $options[$term->term_id] = '- ' . $term->name;
242 - }
243 - }
244 - }
245 - }
246 -
247 290 $fields['commercial_property_type'] = array(
248 - 'type' => 'select',
291 + 'type' => 'commercial_property_type',
249 292 'show_label' => true,
250 293 'before' => '<div class="control control-commercial_property_type commercial-only">',
251 294 'label' => __( 'Type', 'propertyhive' ),
252 - 'options' => $options
253 295 );
254 296 }
255 297
256 298 return $fields;
@@ -258,17 +300,45 @@
258 300
259 301 /**
260 302 * Main function for drawing property enquiry form.
261 303 *
262 - * @param string $id
304 + * @param string $property_id
263 305 * @return void
264 306 */
265 307 function propertyhive_enquiry_form( $property_id = '' )
266 308 {
309 + global $post;
310 +
267 311 $form_controls = ph_get_property_enquiry_form_fields( $property_id );
268 312
269 - $form_controls = apply_filters( 'propertyhive_property_enquiry_form_fields', $form_controls );
313 + $form_controls = apply_filters( 'propertyhive_property_enquiry_form_fields', $form_controls, $property_id );
270 314
315 + $form_controls['property_id'] = array(
316 + 'type' => 'hidden',
317 + 'value' => ( $property_id != '' ? $property_id : $post->ID )
318 + );
319 +
320 + $utm_fields = array( 'utm_source', 'utm_medium', 'utm_term', 'utm_content', 'utm_campaign', 'gclid', 'fbclid' );
321 + foreach ( $utm_fields as $utm_field )
322 + {
323 + $form_controls[$utm_field] = array(
324 + 'type' => 'hidden',
325 + 'value' =>''
326 + );
327 + }
328 +
329 + if ( get_option( 'propertyhive_property_enquiry_form_disclaimer', '' ) != '' )
330 + {
331 + $disclaimer = wp_kses_post( get_option( 'propertyhive_property_enquiry_form_disclaimer', '' ) );
332 +
333 + $form_controls['disclaimer'] = array(
334 + 'type' => 'checkbox',
335 + 'label' => $disclaimer,
336 + 'label_style' => 'width:100%;',
337 + 'required' => true
338 + );
339 + }
340 +
271 341 ph_get_template( 'global/make-enquiry-form.php',array( 'form_controls' => $form_controls ) );
272 342 }
273 343
274 344 /**
@@ -275,8 +345,9 @@
275 345 * Get default fields to be shown on search forms
276 346 *
277 347 * @return array
278 348 */
349 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_get_property_enquiry_form_fields; the established callable name is part of the plugin/extension API and must remain stable.
279 350 function ph_get_property_enquiry_form_fields( $property_id = '' )
280 351 {
281 352 global $post;
282 353
@@ -281,16 +352,13 @@
281 352 global $post;
282 353
283 354 $fields = array();
284 355
285 - $fields['property_id'] = array(
286 - 'type' => 'hidden',
287 - 'value' => ( $property_id != '' ? $property_id : $post->ID )
288 - );
289 -
290 356 $fields['name'] = array(
291 357 'type' => 'text',
292 358 'label' => __( 'Full Name', 'propertyhive' ),
359 + 'show_label' => true,
360 + 'before' => '<div class="control control-name">',
293 361 'required' => true
294 362 );
295 363 if ( is_user_logged_in() )
296 364 {
@@ -301,8 +369,10 @@
301 369
302 370 $fields['email_address'] = array(
303 371 'type' => 'email',
304 372 'label' => __( 'Email Address', 'propertyhive' ),
373 + 'show_label' => true,
374 + 'before' => '<div class="control control-email_address">',
305 375 'required' => true
306 376 );
307 377 if ( is_user_logged_in() )
308 378 {
@@ -313,8 +383,10 @@
313 383
314 384 $fields['telephone_number'] = array(
315 385 'type' => 'text',
316 386 'label' => __( 'Number', 'propertyhive' ),
387 + 'show_label' => true,
388 + 'before' => '<div class="control control-telephone_number">',
317 389 'required' => true
318 390 );
319 391
320 392 $fields['message'] = array(
@@ -319,23 +391,13 @@
319 391
320 392 $fields['message'] = array(
321 393 'type' => 'textarea',
322 394 'label' => __( 'Message', 'propertyhive' ),
395 + 'show_label' => true,
396 + 'before' => '<div class="control control-message">',
323 397 'required' => true
324 398 );
325 399
326 - if ( get_option( 'propertyhive_property_enquiry_form_disclaimer', '' ) != '' )
327 - {
328 - $disclaimer = get_option( 'propertyhive_property_enquiry_form_disclaimer', '' );
329 -
330 - $fields['disclaimer'] = array(
331 - 'type' => 'checkbox',
332 - 'label' => $disclaimer,
333 - 'label_style' => 'width:100%;',
334 - 'required' => true
335 - );
336 - }
337 -
338 400 return $fields;
339 401 }
340 402
341 403 /**
@@ -342,8 +404,9 @@
342 404 * Get default fields to be shown on applicant registration forms
343 405 *
344 406 * @return array
345 407 */
408 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_get_user_details_form_fields; the established callable name is part of the plugin/extension API and must remain stable.
346 409 function ph_get_user_details_form_fields()
347 410 {
348 411 global $post;
349 412
@@ -411,36 +474,13 @@
411 474 * Get default fields to be shown on applicant registration forms
412 475 *
413 476 * @return array
414 477 */
415 -function ph_get_applicant_requirements_form_fields()
478 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_get_applicant_requirements_form_fields; the established callable name is part of the plugin/extension API and must remain stable.
479 +function ph_get_applicant_requirements_form_fields($applicant_profile = false)
416 480 {
417 481 global $post;
418 482
419 - if ( is_user_logged_in() )
420 - {
421 - $current_user = wp_get_current_user();
422 - $applicant_profile = false;
423 -
424 - if ( $current_user instanceof WP_User )
425 - {
426 - $contact = new PH_Contact( '', $current_user->ID );
427 -
428 - if ( is_array($contact->contact_types) && in_array('applicant', $contact->contact_types) )
429 - {
430 - if (
431 - $contact->applicant_profiles != '' &&
432 - $contact->applicant_profiles > 0 &&
433 - $contact->applicant_profile_0 != '' &&
434 - is_array($contact->applicant_profile_0)
435 - )
436 - {
437 - $applicant_profile = $contact->applicant_profile_0;
438 - }
439 - }
440 - }
441 - }
442 -
443 483 $fields = array();
444 484
445 485 $offices = array();
446 486 $value = '';
@@ -478,34 +518,37 @@
478 518 'value' => $value,
479 519 'options' => $offices
480 520 );
481 521
522 + $value = '';
523 +
524 + $ph_departments = ph_get_departments();
482 525 $departments = array();
483 - $value = '';
484 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' )
526 +
527 + $show_residential_fields = false;
528 + $show_commercial_fields = false;
529 + foreach ( $ph_departments as $key => $department )
485 530 {
486 - $departments['residential-sales'] = __( 'Properties To Buy', 'propertyhive' );
487 - if ($value == '' && (get_option( 'propertyhive_primary_department' ) == 'residential-sales' || get_option( 'propertyhive_primary_department' ) === FALSE) )
531 + if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
488 532 {
489 - $value = 'residential-sales';
533 + $departments[$key] = $department;
534 + if ($value == '' && (get_option( 'propertyhive_primary_department' ) == $key || get_option( 'propertyhive_primary_department' ) === FALSE) )
535 + {
536 + $value = $key;
537 + }
538 +
539 + if ( in_array($key, array('residential-sales', 'residential-lettings')) || in_array(ph_get_custom_department_based_on($key), array('residential-sales', 'residential-lettings')) )
540 + {
541 + $show_residential_fields = true;
542 + }
543 +
544 + if ( in_array($key, array('commercial')) || in_array(ph_get_custom_department_based_on($key), array('commercial')) )
545 + {
546 + $show_commercial_fields = true;
547 + }
490 548 }
491 549 }
492 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
493 - {
494 - $departments['residential-lettings'] = __( 'Properties For Rent', 'propertyhive' );
495 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
496 - {
497 - $value = 'residential-lettings';
498 - }
499 - }
500 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
501 - {
502 - $departments['commercial'] = __( 'Commercial Properties', 'propertyhive' );
503 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'commercial')
504 - {
505 - $value = 'commercial';
506 - }
507 - }
550 +
508 551 $fields['department'] = array(
509 552 'type' => 'radio',
510 553 'label' => __( 'Looking For', 'propertyhive' ),
511 554 'required' => true,
@@ -521,9 +564,9 @@
521 564 {
522 565 $fields['department']['type'] = 'hidden';
523 566 }
524 567
525 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' || get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
568 + if ( $show_residential_fields )
526 569 {
527 570 $fields['maximum_price'] = array(
528 571 'type' => 'number',
529 572 'label' => __( 'Maximum Price', 'propertyhive' ),
@@ -563,9 +606,9 @@
563 606 $args = array(
564 607 'hide_empty' => false,
565 608 'parent' => 0
566 609 );
567 - $terms = get_terms( 'property_type', $args );
610 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
568 611
569 612 $options = array();
570 613
571 614 $selected_value = '';
@@ -570,9 +613,9 @@
570 613
571 614 $selected_value = '';
572 615 if ( !empty( $terms ) && !is_wp_error( $terms ) )
573 616 {
574 - $options = array( '' => __( 'All Property Types', 'properthive' ) );
617 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
575 618
576 619 foreach ($terms as $term)
577 620 {
578 621 $options[$term->term_id] = $term->name;
@@ -580,9 +623,9 @@
580 623 $args = array(
581 624 'hide_empty' => false,
582 625 'parent' => $term->term_id
583 626 );
584 - $subterms = get_terms( 'property_type', $args );
627 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
585 628
586 629 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
587 630 {
588 631 foreach ($subterms as $term)
@@ -599,19 +642,20 @@
599 642 'type' => 'select',
600 643 'label' => __( 'Property Type', 'propertyhive' ),
601 644 'before' => '<div class="control control-property_type residential-only">',
602 645 'required' => false,
646 + 'multiselect' => true,
603 647 'options' => $options,
604 648 );
605 649
606 650 if ( is_user_logged_in() && isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
607 651 {
608 - $fields['property_type']['value'] = $applicant_profile['property_types'][0];
652 + $fields['property_type']['value'] = $applicant_profile['property_types'];
609 653 }
610 654 }
611 655 }
612 656
613 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
657 + if ( $show_commercial_fields )
614 658 {
615 659 $fields['available_as_sale'] = array(
616 660 'type' => 'checkbox',
617 661 'label' => __( 'For Sale', 'propertyhive' ),
@@ -661,9 +705,9 @@
661 705 $args = array(
662 706 'hide_empty' => false,
663 707 'parent' => 0
664 708 );
665 - $terms = get_terms( 'commercial_property_type', $args );
709 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
666 710
667 711 $options = array();
668 712
669 713 $selected_value = '';
@@ -668,9 +712,9 @@
668 712
669 713 $selected_value = '';
670 714 if ( !empty( $terms ) && !is_wp_error( $terms ) )
671 715 {
672 - $options = array( '' => __( 'All Property Types', 'properthive' ) );
716 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
673 717
674 718 foreach ($terms as $term)
675 719 {
676 720 $options[$term->term_id] = $term->name;
@@ -678,9 +722,9 @@
678 722 $args = array(
679 723 'hide_empty' => false,
680 724 'parent' => $term->term_id
681 725 );
682 - $subterms = get_terms( 'commercial_property_type', $args );
726 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
683 727
684 728 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
685 729 {
686 730 foreach ($subterms as $term)
@@ -697,63 +741,54 @@
697 741 'type' => 'select',
698 742 'label' => __( 'Property Type', 'propertyhive' ),
699 743 'before' => '<div class="control control-commercial_property_type commercial-only">',
700 744 'required' => false,
745 + 'multiselect' => true,
701 746 'options' => $options,
702 747 );
703 748
704 749 if ( is_user_logged_in() && isset($applicant_profile['commercial_property_types']) && is_array($applicant_profile['commercial_property_types']) && !empty($applicant_profile['commercial_property_types']) )
705 750 {
706 - $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'][0];
751 + $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'];
707 752 }
708 753 }
709 754 }
710 755
711 - $args = array(
712 - 'hide_empty' => false,
713 - 'parent' => 0
714 - );
715 - $terms = get_terms( 'location', $args );
716 -
717 - $options = array();
718 -
719 - $selected_value = '';
720 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
756 + if ( get_option('propertyhive_applicant_locations_type') != 'text' )
721 757 {
722 - $options = array( '' => __( 'All Locations', 'properthive' ) );
758 + $args = array(
759 + 'hide_empty' => false,
760 + 'parent' => 0
761 + );
762 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
723 763
724 - foreach ($terms as $term)
764 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
725 765 {
726 - $options[$term->term_id] = $term->name;
727 -
728 - $args = array(
729 - 'hide_empty' => false,
730 - 'parent' => $term->term_id
766 + $fields['location'] = array(
767 + 'type' => 'location',
768 + 'label' => __( 'Location', 'propertyhive' ),
769 + 'blank_option' => __( 'All Locations', 'propertyhive' ),
770 + 'required' => false,
771 + 'multiselect' => true,
731 772 );
732 - $subterms = get_terms( 'location', $args );
733 773
734 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
774 + if ( is_user_logged_in() && isset($applicant_profile['locations']) && is_array($applicant_profile['locations']) && !empty($applicant_profile['locations']) )
735 775 {
736 - foreach ($subterms as $term)
737 - {
738 - $options[$term->term_id] = '- ' . $term->name;
739 - }
776 + $fields['location']['value'] = $applicant_profile['locations'];
740 777 }
741 778 }
742 779 }
743 -
744 - if ( !empty($options) )
780 + else
745 781 {
746 - $fields['location'] = array(
747 - 'type' => 'select',
782 + $fields['location_text'] = array(
783 + 'type' => 'text',
748 784 'label' => __( 'Location', 'propertyhive' ),
749 - 'required' => false,
750 - 'options' => $options,
785 + 'required' => false
751 786 );
752 787
753 - if ( is_user_logged_in() && isset($applicant_profile['locations']) && is_array($applicant_profile['locations']) && !empty($applicant_profile['locations']) )
788 + if ( is_user_logged_in() && isset($applicant_profile['location_text']) && $applicant_profile['location_text'] != '' )
754 789 {
755 - $fields['location']['value'] = $applicant_profile['locations'][0];
790 + $fields['location_text']['value'] = $applicant_profile['location_text'];
756 791 }
757 792 }
758 793
759 794 $fields['additional_requirements'] = array(
@@ -773,8 +808,9 @@
773 808 * Output individual field
774 809 *
775 810 * @return void
776 811 */
812 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_form_field; the established callable name is part of the plugin/extension API and must remain stable.
777 813 function ph_form_field( $key, $field )
778 814 {
779 815 global $post;
780 816
@@ -787,10 +823,11 @@
787 823 case "date":
788 824 case "number":
789 825 case "password":
790 826 {
827 + $field['id'] = isset( $field['id'] ) ? $field['id'] : $key;
791 828 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
792 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
829 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
793 830 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
794 831 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
795 832 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
796 833 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : ( ( $field['type'] == 'date' ) ? 'dd/mm/yyyy' : '' );
@@ -797,15 +834,17 @@
797 834 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
798 835 $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
799 836
800 837 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
838 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
801 839 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
802 840 {
841 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
803 842 $field['value'] = sanitize_text_field( wp_unslash( $_GET[$key] ) );
804 843 }
805 844 else
806 845 {
807 - if ( isset($post->ID) )
846 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
808 847 {
809 848 $value = get_post_meta( $post->ID, '_' . $key, true );
810 849 if ( $value != '' )
811 850 {
@@ -828,9 +867,9 @@
828 867
829 868 $output .= '<input
830 869 type="' . esc_attr( $field['type'] ) . '"
831 870 name="' . esc_attr( $key ) . '"
832 - id="' . esc_attr( $key ) . '"
871 + id="' . esc_attr( $field['id'] ) . '"
833 872 value="' . esc_attr( $field['value'] ) . '"
834 873 placeholder="' . esc_attr( $field['placeholder'] ) . '"
835 874 class="' . esc_attr( $field['class'] ) . '"
836 875 style="' . esc_attr( $field['style'] ) . '"
@@ -843,9 +882,9 @@
843 882 }
844 883 case "textarea":
845 884 {
846 885 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
847 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
886 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
848 887 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
849 888 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
850 889 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
851 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
@@ -851,15 +890,17 @@
851 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
852 891 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
853 892
854 893 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
894 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
855 895 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
856 896 {
897 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
857 898 $field['value'] = sanitize_textarea_field( wp_unslash( $_GET[$key] ) );
858 899 }
859 900 else
860 901 {
861 - if ( isset($post->ID) )
902 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
862 903 {
863 904 $value = get_post_meta( $post->ID, '_' . $key, true );
864 905 if ( $value != '' )
865 906 {
@@ -885,9 +926,9 @@
885 926 id="' . esc_attr( $key ) . '"
886 927 placeholder="' . esc_attr( $field['placeholder'] ) . '"
887 928 class="' . esc_attr( $field['class'] ) . '"
888 929 ' . ( ($field['required']) ? 'required' : '' ) . '
889 - >' . esc_attr( $field['value'] ) . '</textarea>';
930 + >' . esc_textarea( $field['value'] ) . '</textarea>';
890 931
891 932 $output .= $field['after'];
892 933
893 934 break;
@@ -894,9 +935,9 @@
894 935 }
895 936 case "checkbox":
896 937 {
897 938 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
898 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
939 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
899 940 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
900 941 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
901 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
902 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
@@ -901,8 +942,9 @@
901 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
902 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
903 944 $field['value'] = isset( $field['value'] ) ? $field['value'] : 'yes';
904 945 $field['checked'] = isset( $field['checked'] ) ? $field['checked'] : false;
946 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
905 947 if ( isset( $_GET[$key] ) && sanitize_text_field(wp_unslash($_GET[$key])) == $field['value'] )
906 948 {
907 949 $field['checked'] = true;
908 950 }
@@ -907,9 +949,9 @@
907 949 $field['checked'] = true;
908 950 }
909 951 else
910 952 {
911 - if ( isset($post->ID) )
953 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
912 954 {
913 955 $value = get_post_meta( $post->ID, '_' . $key, true );
914 956 if ( $value == 'yes' )
915 957 {
@@ -939,16 +981,23 @@
939 981 }
940 982 case "radio":
941 983 {
942 984 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
943 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
985 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
944 986 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
987 + $field['before_option'] = isset( $field['before_option'] ) ? $field['before_option'] : '<label>';
988 + $field['after_option'] = isset( $field['after_option'] ) ? $field['after_option'] : '</label>';
989 + $field['before_input'] = isset( $field['before_input'] ) ? $field['before_input'] : '';
990 + $field['after_input'] = isset( $field['after_input'] ) ? $field['after_input'] : '';
945 991 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : false;
946 992 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
993 + $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
947 994
948 995 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
996 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
949 997 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
950 998 {
999 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
951 1000 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
952 1001 }
953 1002
954 1003 $output .= $field['before'];
@@ -954,20 +1003,33 @@
954 1003 $output .= $field['before'];
955 1004
956 1005 if ($field['show_label'])
957 1006 {
958 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1007 + // get first option as 'for'
1008 + $option_key = '';
1009 + foreach ( $field['options'] as $option_key => $value )
1010 + {
1011 + break;
1012 + }
1013 + $output .= '<label for="' . esc_attr( $key ) . '_' . esc_attr( $option_key ) . '">' . $field['label'] . '</label>';
959 1014 }
960 1015
961 1016 foreach ( $field['options'] as $option_key => $value )
962 1017 {
963 - $output .= '<label><input
1018 + $id = esc_attr( $key ) . '_' . esc_attr( $option_key );
1019 + $output .= str_replace("{id}", $id, $field['before_option']);
1020 + $output .= str_replace("{id}", $id, $field['before_input']);
1021 + $output .= '<input
964 1022 type="' . esc_attr( $field['type'] ) . '"
965 1023 name="' . esc_attr( $key ) . '"
1024 + id="' . $id . '"
966 1025 value="' . esc_attr( $option_key ) . '"
967 1026 class="' . esc_attr( $field['class'] ) . '"
968 1027 ' . checked( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
969 - > ' . esc_html( $value ) . '</label>';
1028 + >';
1029 + $output .= str_replace("{id}", $id, $field['after_input']);
1030 + $output .= ' ' . esc_html( $value );
1031 + $output .= str_replace("{id}", $id, $field['after_option']);
970 1032 }
971 1033
972 1034 $output .= $field['after'];
973 1035
@@ -975,23 +1037,31 @@
975 1037 }
976 1038 case "select":
977 1039 {
978 1040 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
979 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1041 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
980 1042 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
981 1043 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
982 1044 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
983 1045 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
984 1046 $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
1047 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
985 1048
1049 + if ( $field['multiselect'] )
1050 + {
1051 + wp_enqueue_script( 'multiselect' );
1052 + }
1053 +
986 1054 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1055 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
987 1056 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
988 1057 {
1058 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
989 1059 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
990 1060 }
991 1061 else
992 1062 {
993 - if ( isset($post->ID) )
1063 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
994 1064 {
995 1065 $value = get_post_meta( $post->ID, '_' . $key, true );
996 1066 if ( $value != '' )
997 1067 {
@@ -1011,20 +1081,54 @@
1011 1081 }
1012 1082 $output .= '</label>';
1013 1083 }
1014 1084
1085 + $blank_option = '';
1086 + foreach ( $field['options'] as $option_key => $value )
1087 + {
1088 + if ( $field['multiselect'] && $option_key == '' )
1089 + {
1090 + $blank_option = $value;
1091 + continue;
1092 + }
1093 + }
1094 +
1015 1095 $output .= '<select
1016 - name="' . esc_attr( $key ) . '"
1096 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1017 1097 id="' . esc_attr( $key ) . '"
1018 - class="' . esc_attr( $field['class'] ) . '"
1098 + class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1099 + ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1100 + data-blank-option="' . esc_attr($blank_option) . '"
1019 1101 >';
1020 1102
1021 1103 foreach ( $field['options'] as $option_key => $value )
1022 1104 {
1105 + if ( $field['multiselect'] && $option_key == '' )
1106 + {
1107 + // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1108 + continue;
1109 + }
1110 +
1023 1111 $output .= '<option
1024 - value="' . esc_attr( $option_key ) . '"
1025 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
1026 - >' . esc_html( $value ) . '</option>';
1112 + value="' . esc_attr( $option_key ) . '"';
1113 + if ( !$field['multiselect'] )
1114 + {
1115 + $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1116 + }
1117 + else
1118 + {
1119 + if (
1120 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1121 + ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1122 + ||
1123 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1124 + ( !isset($_REQUEST[$key]) && is_array($field['value']) && in_array($option_key, $field['value']) )
1125 + )
1126 + {
1127 + $output .= ' selected';
1128 + }
1129 + }
1130 + $output .= '>' . esc_html( $value ) . '</option>';
1027 1131 }
1028 1132
1029 1133 $output .= '</select>';
1030 1134
@@ -1036,16 +1140,25 @@
1036 1140 {
1037 1141 $key = 'officeID';
1038 1142
1039 1143 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1040 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1144 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1041 1145 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1042 1146 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1043 1147 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1148 + $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1149 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1044 1150
1151 + if ( $field['multiselect'] )
1152 + {
1153 + wp_enqueue_script( 'multiselect' );
1154 + }
1155 +
1045 1156 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1157 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1046 1158 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1047 1159 {
1160 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1048 1161 $field['value'] = (int)$_GET[$key];
1049 1162 }
1050 1163
1051 1164 $output .= $field['before'];
@@ -1055,19 +1168,24 @@
1055 1168 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1056 1169 }
1057 1170
1058 1171 $output .= '<select
1059 - name="' . esc_attr( $key ) . '"
1172 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1060 1173 id="' . esc_attr( $key ) . '"
1061 - class="' . esc_attr( $field['class'] ) . '"
1062 - >';
1174 + class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1175 + ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1176 + data-blank-option="' . esc_attr( $field['blank_option'] ) . '"
1177 + >';
1063 1178
1064 - $output .= '<option
1179 + if ( !$field['multiselect'] )
1180 + {
1181 + $output .= '<option
1065 1182 value=""
1066 1183 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
1067 - >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
1184 + >' . esc_html( $field['blank_option'] ) . '</option>';
1185 + }
1068 1186
1069 - $args = array(
1187 + $args = array(
1070 1188 'post_type' => 'office',
1071 1189 'nopaging' => true,
1072 1190 'orderby' => 'title',
1073 1191 'order' => 'ASC'
@@ -1080,11 +1198,22 @@
1080 1198 {
1081 1199 $office_query->the_post();
1082 1200
1083 1201 $output .= '<option
1084 - value="' . esc_attr( $post->ID ) . '"
1085 - ' . selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false ) . '
1086 - >' . esc_html( get_the_title() ) . '</option>';
1202 + value="' . esc_attr( $post->ID ) . '" ';
1203 + if ( !$field['multiselect'] )
1204 + {
1205 + $output .= selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false );
1206 + }
1207 + else
1208 + {
1209 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1210 + if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($post->ID, $_REQUEST[$key]) )
1211 + {
1212 + $output .= ' selected';
1213 + }
1214 + }
1215 + $output .= '>' . esc_html( get_the_title() ) . '</option>';
1087 1216
1088 1217 }
1089 1218 }
1090 1219 wp_reset_postdata();
@@ -1097,16 +1226,18 @@
1097 1226 }
1098 1227 case "country":
1099 1228 {
1100 1229 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1101 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1230 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1102 1231 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1103 1232 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1104 1233 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1105 1234
1106 1235 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1236 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1107 1237 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1108 1238 {
1239 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1109 1240 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1110 1241 }
1111 1242
1112 1243 $output .= $field['before'];
@@ -1151,24 +1282,152 @@
1151 1282 $output .= $field['after'];
1152 1283
1153 1284 break;
1154 1285 }
1286 + case "slider":
1287 + {
1288 + wp_enqueue_script('jquery');
1289 + wp_enqueue_script('jquery-ui-core');
1290 + wp_enqueue_script('jquery-ui-slider');
1291 + wp_enqueue_script( 'jquery-touch-punch' );
1292 + wp_enqueue_style( 'jquery-ui-style', PH()->plugin_url() . '/assets/css/jquery-ui/jquery-ui.css', array(), PH_VERSION );
1293 +
1294 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1295 + $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1296 + $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1297 + $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1298 + $field['min'] = isset( $field['min'] ) ? $field['min'] : '';
1299 + $field['max'] = isset( $field['max'] ) ? $field['max'] : '';
1300 + $field['step'] = isset( $field['step'] ) ? $field['step'] : '1';
1301 +
1302 + $output .= $field['before'];
1303 +
1304 + if ($field['show_label'])
1305 + {
1306 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'];
1307 + $output .= ' - <span id="search-form-slider-value-' . esc_attr( $key ) . '" class="search-form-slider-value search-form-slider-value-' . esc_attr( $key ) . '"></span>';
1308 + $output .= '</label>';
1309 + }
1310 +
1311 + $output .= '<div id="search-form-slider-' . esc_attr( $key ) . '" class="search-form-slider search-form-slider-' . esc_attr( $key ) . '" style="min-width:150px;"></div>';
1312 +
1313 + $field_name = str_replace("_slider", "", $key);
1314 + // Read-only search preferences do not require a nonce.
1315 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1316 + $minimum = isset( $_GET['minimum_' . $field_name] ) && is_string( $_GET['minimum_' . $field_name] ) ? sanitize_text_field( wp_unslash( $_GET['minimum_' . $field_name] ) ) : '';
1317 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1318 + $maximum = isset( $_GET['maximum_' . $field_name] ) && is_string( $_GET['maximum_' . $field_name] ) ? sanitize_text_field( wp_unslash( $_GET['maximum_' . $field_name] ) ) : '';
1319 + $output .= '<input type="hidden" name="minimum_' . esc_attr( $field_name ) . '" class="min_slider_value-' . esc_attr( $key ) . '" id="min_slider_value-' . esc_attr( $key ) . '" value="' . esc_attr( $minimum ) . '">';
1320 + $output .= '<input type="hidden" name="maximum_' . esc_attr( $field_name ) . '" class="max_slider_value-' . esc_attr( $key ) . '" id="max_slider_value-' . esc_attr( $key ) . '" value="' . esc_attr( $maximum ) . '">';
1321 +
1322 + $output .= $field['after'];
1323 +
1324 + $value = '';
1325 + $prefix = '';
1326 + $suffix = '';
1327 +
1328 + $slider_keys = apply_filters('propertyhive_search_form_currency_slider_keys', [
1329 + 'price_slider',
1330 + 'rent_slider',
1331 + ]);
1332 +
1333 + if ( in_array($key, $slider_keys, true) )
1334 + {
1335 + $prefix = '£';
1336 +
1337 + $search_form_currency = get_option( 'propertyhive_search_form_currency', 'GBP' );
1338 +
1339 + $ph_countries = new PH_Countries();
1340 + $countries = $ph_countries->countries;
1341 +
1342 + foreach ( $countries as $country_code => $country )
1343 + {
1344 + if ( isset($country['currency_code']) && $country['currency_code'] == $search_form_currency )
1345 + {
1346 + if ( $country['currency_prefix'] === true )
1347 + {
1348 + $prefix = $country['currency_symbol'];
1349 + $suffix = '';
1350 + }
1351 + else
1352 + {
1353 + $prefix = '';
1354 + $suffix = $country['currency_symbol'];
1355 + }
1356 + break;
1357 + }
1358 + }
1359 + }
1360 +
1361 + $js_key = wp_json_encode( sanitize_html_class( $key ) );
1362 + $js_prefix = wp_json_encode( html_entity_decode( $prefix, ENT_QUOTES, 'UTF-8' ) );
1363 + $js_suffix = wp_json_encode( html_entity_decode( $suffix, ENT_QUOTES, 'UTF-8' ) );
1364 +
1365 + if ( $field['min'] != '' && $field['max'] != '' )
1366 + {
1367 + $value = 'values: [ ' . ( $minimum !== '' ? (float) $minimum : (float)$field['min'] ) . ', ' . ( $maximum !== '' ? (float) $maximum : (float)$field['max'] ) . ' ],';
1368 + }
1369 +
1370 + $output .= '<script>
1371 + jQuery(document).ready(function()
1372 + {
1373 + var key = ' . $js_key . ';
1374 + var prefix = ' . $js_prefix . ';
1375 + var suffix = ' . $js_suffix . ';
1376 +
1377 + jQuery(".search-form-slider-" + key).each(function(index)
1378 + {
1379 + var $slider = jQuery(this);
1380 +
1381 + $slider.slider({
1382 + range: ' . ( ( $field['min'] != '' && $field['max'] != '' ) ? 'true' : 'false' ) . ',
1383 + step: ' . (float) $field['step'] . ',
1384 + ' . ( $field['min'] != '' ? 'min: ' . (float) $field['min'] . ',' : '' ) . '
1385 + ' . ( $field['max'] != '' ? 'max: ' . (float) $field['max'] . ',' : '' ) . '
1386 + ' . $value . '
1387 + slide: function( event, ui ) {
1388 + var min = ui.values[0].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
1389 + var max = ui.values[1].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
1390 +
1391 + $slider.closest("form").find(".search-form-slider-value-" + key).text(
1392 + prefix + min + suffix + " - " + prefix + max + suffix
1393 + );
1394 +
1395 + $slider.closest("form").find(".min_slider_value-" + key).val(ui.values[0]);
1396 + $slider.closest("form").find(".max_slider_value-" + key).val(ui.values[1]);
1397 + }
1398 + });
1399 +
1400 + var initialMin = $slider.slider("values", 0).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
1401 + var initialMax = $slider.slider("values", 1).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
1402 +
1403 + $slider.closest("form").find(".search-form-slider-value-" + key).text(
1404 + prefix + initialMin + suffix + " - " + prefix + initialMax + suffix
1405 + );
1406 + });
1407 + });
1408 + </script>';
1409 +
1410 + break;
1411 + }
1155 1412 case "hidden":
1156 1413 {
1157 1414 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1158 1415 $field['name'] = isset( $field['name'] ) ? $field['name'] : $key;
1416 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1159 1417 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1160 1418 {
1419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1161 1420 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1162 1421 }
1163 1422
1164 - $output .= '<input type="hidden" name="' . esc_attr( $field['name'] ) . '" value="' . $field['value'] . '">';
1423 + $output .= '<input type="hidden" name="' . esc_attr( $field['name'] ) . '" value="' . esc_attr($field['value']) . '">';
1165 1424 break;
1166 1425 }
1167 1426 case "html":
1168 1427 {
1169 1428 $field['html'] = isset( $field['html'] ) ? $field['html'] : '';
1170 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1429 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1171 1430 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1172 1431
1173 1432 $output .= $field['before'];
1174 1433 $output .= $field['html'];
@@ -1177,12 +1436,80 @@
1177 1436 break;
1178 1437 }
1179 1438 case "recaptcha":
1180 1439 {
1181 - $field['site_key'] = isset( $field['site_key'] ) ? $field['site_key'] : '';
1440 + $site_key = isset( $field['site_key'] ) && is_string( $field['site_key'] ) ? $field['site_key'] : '';
1441 + // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent, WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Provider maintains this API endpoint without a plugin version. Required by the configured Google reCAPTCHA service.
1442 + wp_enqueue_script( 'propertyhive-recaptcha', 'https://www.google.com/recaptcha/api.js', array(), null, true );
1443 + $output .= '<div class="g-recaptcha" data-sitekey="' . esc_attr( $site_key ) . '"></div>';
1444 + break;
1445 + }
1446 + case "recaptcha-v3":
1447 + {
1448 + $site_key = isset( $field['site_key'] ) && is_string( $field['site_key'] ) ? $field['site_key'] : '';
1449 + // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent, WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Provider maintains this API endpoint without a plugin version. Required by the configured Google reCAPTCHA service.
1450 + wp_enqueue_script( 'propertyhive-recaptcha-v3', add_query_arg( 'render', $site_key, 'https://www.google.com/recaptcha/api.js' ), array(), null, true );
1451 + wp_add_inline_script( 'propertyhive-recaptcha-v3',
1452 + 'grecaptcha.ready(function() { grecaptcha.execute(' . wp_json_encode( $site_key, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ', {action:"submit"}).then(function(token) { document.querySelectorAll("[name=g-recaptcha-response]").forEach(function(elem) { elem.value = token; }); }); });'
1453 + );
1454 + $output .= '<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">';
1455 + break;
1456 + }
1457 + case "hCaptcha":
1458 + {
1459 + $site_key = isset( $field['site_key'] ) && is_string( $field['site_key'] ) ? $field['site_key'] : '';
1460 + // phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent, WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Provider maintains this API endpoint without a plugin version. Required by the configured hCaptcha service.
1461 + wp_enqueue_script( 'propertyhive-hcaptcha', 'https://js.hcaptcha.com/1/api.js', array(), null, true );
1462 + $output .= '<div class="h-captcha" data-sitekey="' . esc_attr( $site_key ) . '"></div>';
1463 + break;
1464 + }
1465 + case "turnstile":
1466 + {
1467 + $site_key = isset( $field['site_key'] ) && is_string( $field['site_key'] ) ? $field['site_key'] : '';
1468 + $output .= '<div class="turnstile" data-sitekey="' . esc_attr( $site_key ) . '"></div>';
1469 + break;
1470 + }
1471 + case "daterange":
1472 + {
1473 + wp_enqueue_script( 'moment' );
1474 + wp_enqueue_script( 'daterangepicker.js', PH()->plugin_url() . '/assets/js/daterangepicker/daterangepicker.js', array( 'jquery', 'moment' ), '3.1.0', true );
1475 + wp_enqueue_style( 'daterangepicker.css', PH()->plugin_url() . '/assets/js/daterangepicker/daterangepicker.css', array(), '3.1.0' );
1182 1476
1183 - $output .= '<script src="https://www.google.com/recaptcha/api.js"></script>
1184 - <div class="g-recaptcha" data-sitekey="' . $field['site_key'] . '"></div>';
1477 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1478 + $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1479 +
1480 + $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1481 + $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1482 +
1483 + $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1484 + $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
1485 + $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1486 + $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
1487 +
1488 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1489 + if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1490 + {
1491 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1492 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1493 + }
1494 +
1495 + $output .= $field['before'];
1496 +
1497 + if ($field['show_label'])
1498 + {
1499 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1500 + }
1501 +
1502 + $output .= '<input type="text" autocomplete="off"
1503 + name="' . esc_attr( $key ) . '"
1504 + id="' . esc_attr( $key ) . '"
1505 + value="' . esc_attr( $field['value'] ) . '"
1506 + style="' . esc_attr( $field['style'] ) . '"
1507 + class="' . esc_attr( $field['class'] ) . '"
1508 + placeholder="' . esc_attr( $field['placeholder'] ) . '"
1509 + />';
1510 + $output .= $field['after'];
1511 +
1185 1512 break;
1186 1513 }
1187 1514 default:
1188 1515 {
@@ -1188,70 +1515,200 @@
1188 1515 {
1189 1516 if ( taxonomy_exists($field['type']) )
1190 1517 {
1191 1518 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1192 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1519 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1193 1520 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1194 1521 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1195 1522 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1196 1523 $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1524 + $field['parent_terms_only'] = isset( $field['parent_terms_only'] ) ? $field['parent_terms_only'] : false;
1525 + $field['hide_empty'] = isset( $field['hide_empty'] ) ? $field['hide_empty'] : false;
1526 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1527 + $field['dynamic_population'] = ( isset( $field['dynamic_population'] ) && $field['type'] == 'location' && $field['parent_terms_only'] === false && $field['multiselect'] === false ) ? $field['dynamic_population'] : false; // only applies to location
1197 1528
1198 - $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1199 - if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1529 + if ( $field['multiselect'] )
1200 1530 {
1201 - $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1531 + wp_enqueue_script( 'multiselect' );
1202 1532 }
1203 -
1204 - $output .= $field['before'];
1205 -
1206 - if ($field['show_label'])
1207 - {
1208 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1209 - }
1210 -
1211 - $output .= '<select
1212 - name="' . esc_attr( $key ) . '"
1213 - id="' . esc_attr( $key ) . '"
1214 - class="' . esc_attr( $field['class'] ) . '"
1215 - >';
1216 -
1217 - $options = array( '' => $field['blank_option'] );
1533 +
1534 + $options = array(
1535 + '' => array(
1536 + 'label' => $field['blank_option'],
1537 + 'parent' => 0
1538 + )
1539 + );
1218 1540 $args = array(
1219 - 'hide_empty' => false,
1541 + 'hide_empty' => $field['hide_empty'],
1220 1542 'parent' => 0
1221 1543 );
1222 - $terms = get_terms( $field['type'], $args );
1544 + $args = apply_filters( 'propertyhive_form_taxonomy_terms_args', $args, $field );
1545 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $field['type'] ) ) );
1223 1546
1224 - $selected_value = '';
1547 + $levels_of_taxonomy = 1;
1225 1548 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1226 1549 {
1227 1550 foreach ($terms as $term)
1228 1551 {
1229 - $options[$term->term_id] = $term->name;
1552 + if ( isset($field['hide_empty']) && $field['hide_empty'] === true )
1553 + {
1554 + $empty_check_args = array(
1555 + 'post_type' => 'property',
1556 + 'posts_per_page' => 1,
1557 + 'fields' => 'ids',
1558 + 'no_found_rows' => true,
1559 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1560 + 'meta_query' => array(
1561 + array(
1562 + 'key' => '_on_market',
1563 + 'value' => 'yes',
1564 + ),
1565 + ),
1566 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1567 + 'tax_query' => array(
1568 + array(
1569 + 'taxonomy' => $field['type'],
1570 + 'field' => 'term_id',
1571 + 'terms' => $term->term_id,
1572 + ),
1573 + ),
1574 + );
1230 1575
1231 - $args = array(
1232 - 'hide_empty' => false,
1233 - 'parent' => $term->term_id
1576 + $empty_check_args = apply_filters( 'propertyhive_taxonomy_hide_empty_args', $empty_check_args, $field, $term->term_id );
1577 +
1578 + $empty_check_query = new WP_Query( $empty_check_args );
1579 +
1580 + if ( !$empty_check_query->have_posts() )
1581 + {
1582 + continue;
1583 + }
1584 + }
1585 +
1586 + $options[(int)$term->term_id] = array(
1587 + 'label' => $term->name,
1588 + 'parent' => 0
1234 1589 );
1235 - $subterms = get_terms( $field['type'], $args );
1236 1590
1237 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1591 + if ($field['dynamic_population'])
1592 + $levels_of_taxonomy = max(1, $levels_of_taxonomy);
1593 +
1594 + if (
1595 + !isset($field['parent_terms_only'])
1596 + ||
1597 + (
1598 + isset($field['parent_terms_only']) &&
1599 + $field['parent_terms_only'] === false
1600 + )
1601 + )
1238 1602 {
1239 - foreach ($subterms as $term)
1603 + $args = array(
1604 + 'hide_empty' => $field['hide_empty'],
1605 + 'parent' => $term->term_id,
1606 + );
1607 + $args = apply_filters( 'propertyhive_form_taxonomy_terms_args', $args, $field );
1608 + $args = apply_filters( 'propertyhive_form_taxonomy_subterms_args', $args, $field );
1609 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $field['type'] ) ) );
1610 +
1611 + if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1240 1612 {
1241 - $options[$term->term_id] = '- ' . $term->name;
1613 + foreach ($subterms as $subterm)
1614 + {
1615 + if ( isset($field['hide_empty']) && $field['hide_empty'] === true )
1616 + {
1617 + $empty_check_args = array(
1618 + 'post_type' => 'property',
1619 + 'posts_per_page' => 1,
1620 + 'fields' => 'ids',
1621 + 'no_found_rows' => true,
1622 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1623 + 'meta_query' => array(
1624 + array(
1625 + 'key' => '_on_market',
1626 + 'value' => 'yes',
1627 + ),
1628 + ),
1629 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1630 + 'tax_query' => array(
1631 + array(
1632 + 'taxonomy' => $field['type'],
1633 + 'field' => 'term_id',
1634 + 'terms' => $subterm->term_id,
1635 + ),
1636 + ),
1637 + );
1242 1638
1243 - $args = array(
1244 - 'hide_empty' => false,
1245 - 'parent' => $term->term_id
1246 - );
1247 - $subsubterms = get_terms( $field['type'], $args );
1639 + $empty_check_args = apply_filters( 'propertyhive_taxonomy_hide_empty_args', $empty_check_args, $field, $subterm->term_id );
1248 1640
1249 - if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1250 - {
1251 - foreach ($subsubterms as $term)
1641 + $empty_check_query = new WP_Query( $empty_check_args );
1642 +
1643 + if ( !$empty_check_query->have_posts() )
1644 + {
1645 + continue;
1646 + }
1647 + }
1648 +
1649 + $options[(int)$subterm->term_id] = array(
1650 + 'label' => ( !$field['dynamic_population'] ? '- ' : '' ) . $subterm->name,
1651 + 'parent' => (int)$term->term_id,
1652 + );
1653 +
1654 + if ($field['dynamic_population'])
1655 + $levels_of_taxonomy = max(2, $levels_of_taxonomy);
1656 +
1657 + $args = array(
1658 + 'hide_empty' => $field['hide_empty'],
1659 + 'parent' => (int)$subterm->term_id
1660 + );
1661 + $args = apply_filters( 'propertyhive_form_taxonomy_terms_args', $args, $field );
1662 + $args = apply_filters( 'propertyhive_form_taxonomy_subsubterms_args', $args, $field );
1663 + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $field['type'] ) ) );
1664 +
1665 + if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1252 1666 {
1253 - $options[$term->term_id] = '- ' . $term->name;
1667 + foreach ($subsubterms as $subsubterm)
1668 + {
1669 + if ( isset($field['hide_empty']) && $field['hide_empty'] === true )
1670 + {
1671 + $empty_check_args = array(
1672 + 'post_type' => 'property',
1673 + 'posts_per_page' => 1,
1674 + 'fields' => 'ids',
1675 + 'no_found_rows' => true,
1676 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1677 + 'meta_query' => array(
1678 + array(
1679 + 'key' => '_on_market',
1680 + 'value' => 'yes',
1681 + ),
1682 + ),
1683 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1684 + 'tax_query' => array(
1685 + array(
1686 + 'taxonomy' => $field['type'],
1687 + 'field' => 'term_id',
1688 + 'terms' => $subsubterm->term_id,
1689 + ),
1690 + ),
1691 + );
1692 +
1693 + $empty_check_args = apply_filters( 'propertyhive_taxonomy_hide_empty_args', $empty_check_args, $field, $subsubterm->term_id );
1694 +
1695 + $empty_check_query = new WP_Query( $empty_check_args );
1696 +
1697 + if ( !$empty_check_query->have_posts() )
1698 + {
1699 + continue;
1700 + }
1701 + }
1702 +
1703 + $options[(int)$subsubterm->term_id] = array(
1704 + 'label' => ( !$field['dynamic_population'] ? '- - ' : '' ) . $subsubterm->name,
1705 + 'parent' => (int)$subterm->term_id,
1706 + );
1707 +
1708 + if ($field['dynamic_population'])
1709 + $levels_of_taxonomy = max(3, $levels_of_taxonomy);
1710 + }
1254 1711 }
1255 1712 }
1256 1713 }
1257 1714 }
@@ -1257,21 +1714,112 @@
1257 1714 }
1258 1715 }
1259 1716 }
1260 1717
1261 - foreach ( $options as $option_key => $value )
1718 + if ( $field['dynamic_population'] )
1262 1719 {
1263 - $output .= '<option
1264 - value="' . esc_attr( $option_key ) . '"
1265 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
1266 - >' . esc_html( $value ) . '</option>';
1720 + wp_localize_script( 'propertyhive_dynamic_population', 'propertyhive_dynamic_population_params', array(
1721 + 'options' => $options,
1722 + 'levels_of_taxonomy' => $levels_of_taxonomy,
1723 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1724 + 'value' => isset($_GET[$field['type']]) ? ph_clean( wp_unslash( $_GET[$field['type']] ) ) : '',
1725 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1726 + 'other_values' => ( isset($_GET['other_' . $field['type']]) && is_array($_GET['other_' . $field['type']]) && !empty($_GET['other_' . $field['type']]) ) ? array_filter( array_filter( ph_clean( wp_unslash( $_GET['other_' . $field['type']] ) ) ), 'is_scalar' ) : array(),
1727 + 'taxonomy' => $field['type'],
1728 + ) );
1729 + wp_enqueue_script( 'propertyhive_dynamic_population' );
1267 1730 }
1268 1731
1269 - $output .= '</select>';
1732 + $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1733 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1734 + if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1735 + {
1736 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1737 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1738 + }
1270 1739
1271 - $output .= $field['after'];
1740 + for ( $level_i = 1; $level_i <= $levels_of_taxonomy; ++$level_i )
1741 + {
1742 + $output .= $field['before'];
1743 +
1744 + if ($field['show_label'])
1745 + {
1746 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1747 + }
1748 +
1749 + $output .= '<select
1750 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1751 + id="' . esc_attr( $key ) . '"
1752 + class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1753 + ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) .
1754 + ( $field['dynamic_population'] ? ' data-dynamic-population-level="' . $level_i . '"' : '' ) .
1755 + ( ( $field['dynamic_population'] && $level_i > 1 ) ? ' disabled' : '' ) . '
1756 + data-blank-option="' . esc_attr($field['blank_option']) . '"
1757 + >';
1758 +
1759 + if ( $level_i == 1 )
1760 + {
1761 + foreach ( $options as $option_key => $value )
1762 + {
1763 + if ( $field['multiselect'] && $option_key == '' )
1764 + {
1765 + // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1766 + continue;
1767 + }
1768 +
1769 + if ( $field['dynamic_population'] && $value['parent'] != '0' )
1770 + {
1771 + continue;
1772 + }
1773 +
1774 + $output .= '<option
1775 + value="' . esc_attr( $option_key ) . '"';
1776 + if ( !$field['multiselect'] )
1777 + {
1778 + $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1779 + }
1780 + else
1781 + {
1782 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1783 + if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1784 + {
1785 + $output .= ' selected';
1786 + }
1787 + elseif ( is_array($field['value']) && in_array($option_key, $field['value']) )
1788 + {
1789 + $output .= ' selected';
1790 + }
1791 + }
1792 + $output .= '>' . esc_html( $value['label'] ) . '</option>';
1793 + }
1794 + }
1795 +
1796 + $output .= '</select>';
1797 +
1798 + $output .= $field['after'];
1799 +
1800 + if ( $field['type'] == 'availability' )
1801 + {
1802 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
1803 + if ( !is_array($availability_departments) ) { $availability_departments = array(); }
1804 +
1805 + if ( !empty($availability_departments) )
1806 + {
1807 +?>
1808 +<script>
1809 +<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial availability selection, reduced to an integer before JavaScript output. ?>
1810 +var selected_availability = '<?php echo ( isset($_REQUEST[$key]) && is_scalar( $_REQUEST[$key] ) && $_REQUEST[$key] != '' ? (int)$_REQUEST[$key] : '' ); ?>';
1811 +var availability_departments = <?php echo wp_json_encode( $availability_departments , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1812 +var availabilities = <?php echo wp_json_encode( $options , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1813 +var availabilities_order = <?php echo wp_json_encode( array_keys($options) , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1814 +</script>
1815 +<?php
1816 + }
1817 + }
1818 + }
1272 1819 }
1273 1820 }
1274 1821 }
1275 1822
1823 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Control values and attributes are escaped while assembling the markup above; labels/wrappers and the HTML control are trusted PHP presentation arguments (saved frontend labels are sanitized before extension filters).
1276 1824 echo $output;
1277 -}
1825 +}