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 +782 -225 1.4.472.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,8 +88,9 @@
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
@@ -88,8 +111,68 @@
88 111 }
89 112 }
90 113 }
91 114
115 + $sales_department_active = false;
116 + if ( array_key_exists('residential-sales', $departments) )
117 + {
118 + $sales_department_active = true;
119 + }
120 + else
121 + {
122 + $custom_departments = ph_get_custom_departments();
123 + if ( !empty($custom_departments) )
124 + {
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 + }
132 + }
133 + }
134 +
135 + $lettings_department_active = false;
136 + if ( array_key_exists('residential-lettings', $departments) )
137 + {
138 + $lettings_department_active = true;
139 + }
140 + else
141 + {
142 + $custom_departments = ph_get_custom_departments();
143 + if ( !empty($custom_departments) )
144 + {
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 + }
152 + }
153 + }
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 +
92 175 $fields['department'] = array(
93 176 'type' => 'radio',
94 177 'options' => $department_options,
95 178 'value' => $default_value
@@ -94,11 +177,11 @@
94 177 'options' => $department_options,
95 178 'value' => $default_value
96 179 );
97 180
98 - if ( array_key_exists('residential-sales', $departments) || array_key_exists('residential-lettings', $departments) )
181 + if ( $sales_department_active || $lettings_department_active )
99 182 {
100 - if ( array_key_exists('residential-sales', $departments) )
183 + if ( $sales_department_active )
101 184 {
102 185 $prices = array(
103 186 '' => __( 'No preference', 'propertyhive' ),
104 187 '100000' => '&pound;100,000',
@@ -127,9 +210,9 @@
127 210 'options' => $prices
128 211 );
129 212 }
130 213
131 - if ( array_key_exists('residential-lettings', $departments) )
214 + if ( $lettings_department_active )
132 215 {
133 216 $prices = array(
134 217 '' => __( 'No preference', 'propertyhive' ),
135 218 '500' => '&pound;500 PCM',
@@ -173,9 +256,9 @@
173 256 'label' => __( 'Type', 'propertyhive' ),
174 257 );
175 258 }
176 259
177 - if ( array_key_exists('commercial', $departments) )
260 + if ( $commercial_department_active )
178 261 {
179 262 $sizes = array(
180 263 '' => __( 'No preference', 'propertyhive' ),
181 264 '250' => '250 sq ft',
@@ -203,45 +286,13 @@
203 286 'before' => '<div class="control control-maximum_floor_area commercial-only">',
204 287 'options' => $sizes
205 288 );
206 289
207 - // Property Type
208 - $options = array( '' => __( 'No preference', 'propertyhive' ) );
209 - $args = array(
210 - 'hide_empty' => false,
211 - 'parent' => 0
212 - );
213 - $terms = get_terms( 'commercial_property_type', $args );
214 -
215 - $selected_value = '';
216 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
217 - {
218 - foreach ($terms as $term)
219 - {
220 - $options[$term->term_id] = $term->name;
221 -
222 - $args = array(
223 - 'hide_empty' => false,
224 - 'parent' => $term->term_id
225 - );
226 - $subterms = get_terms( 'commercial_property_type', $args );
227 -
228 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
229 - {
230 - foreach ($subterms as $term)
231 - {
232 - $options[$term->term_id] = '- ' . $term->name;
233 - }
234 - }
235 - }
236 - }
237 -
238 290 $fields['commercial_property_type'] = array(
239 - 'type' => 'select',
291 + 'type' => 'commercial_property_type',
240 292 'show_label' => true,
241 293 'before' => '<div class="control control-commercial_property_type commercial-only">',
242 294 'label' => __( 'Type', 'propertyhive' ),
243 - 'options' => $options
244 295 );
245 296 }
246 297
247 298 return $fields;
@@ -249,17 +300,45 @@
249 300
250 301 /**
251 302 * Main function for drawing property enquiry form.
252 303 *
253 - * @param string $id
304 + * @param string $property_id
254 305 * @return void
255 306 */
256 307 function propertyhive_enquiry_form( $property_id = '' )
257 308 {
309 + global $post;
310 +
258 311 $form_controls = ph_get_property_enquiry_form_fields( $property_id );
259 312
260 - $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 );
261 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 +
262 341 ph_get_template( 'global/make-enquiry-form.php',array( 'form_controls' => $form_controls ) );
263 342 }
264 343
265 344 /**
@@ -266,8 +345,9 @@
266 345 * Get default fields to be shown on search forms
267 346 *
268 347 * @return array
269 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.
270 350 function ph_get_property_enquiry_form_fields( $property_id = '' )
271 351 {
272 352 global $post;
273 353
@@ -272,16 +352,13 @@
272 352 global $post;
273 353
274 354 $fields = array();
275 355
276 - $fields['property_id'] = array(
277 - 'type' => 'hidden',
278 - 'value' => ( $property_id != '' ? $property_id : $post->ID )
279 - );
280 -
281 356 $fields['name'] = array(
282 357 'type' => 'text',
283 358 'label' => __( 'Full Name', 'propertyhive' ),
359 + 'show_label' => true,
360 + 'before' => '<div class="control control-name">',
284 361 'required' => true
285 362 );
286 363 if ( is_user_logged_in() )
287 364 {
@@ -292,8 +369,10 @@
292 369
293 370 $fields['email_address'] = array(
294 371 'type' => 'email',
295 372 'label' => __( 'Email Address', 'propertyhive' ),
373 + 'show_label' => true,
374 + 'before' => '<div class="control control-email_address">',
296 375 'required' => true
297 376 );
298 377 if ( is_user_logged_in() )
299 378 {
@@ -304,8 +383,10 @@
304 383
305 384 $fields['telephone_number'] = array(
306 385 'type' => 'text',
307 386 'label' => __( 'Number', 'propertyhive' ),
387 + 'show_label' => true,
388 + 'before' => '<div class="control control-telephone_number">',
308 389 'required' => true
309 390 );
310 391
311 392 $fields['message'] = array(
@@ -310,23 +391,13 @@
310 391
311 392 $fields['message'] = array(
312 393 'type' => 'textarea',
313 394 'label' => __( 'Message', 'propertyhive' ),
395 + 'show_label' => true,
396 + 'before' => '<div class="control control-message">',
314 397 'required' => true
315 398 );
316 399
317 - if ( get_option( 'propertyhive_property_enquiry_form_disclaimer', '' ) != '' )
318 - {
319 - $disclaimer = get_option( 'propertyhive_property_enquiry_form_disclaimer', '' );
320 -
321 - $fields['disclaimer'] = array(
322 - 'type' => 'checkbox',
323 - 'label' => $disclaimer,
324 - 'label_style' => 'width:100%;',
325 - 'required' => true
326 - );
327 - }
328 -
329 400 return $fields;
330 401 }
331 402
332 403 /**
@@ -333,8 +404,9 @@
333 404 * Get default fields to be shown on applicant registration forms
334 405 *
335 406 * @return array
336 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.
337 409 function ph_get_user_details_form_fields()
338 410 {
339 411 global $post;
340 412
@@ -402,36 +474,13 @@
402 474 * Get default fields to be shown on applicant registration forms
403 475 *
404 476 * @return array
405 477 */
406 -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)
407 480 {
408 481 global $post;
409 482
410 - if ( is_user_logged_in() )
411 - {
412 - $current_user = wp_get_current_user();
413 - $applicant_profile = false;
414 -
415 - if ( $current_user instanceof WP_User )
416 - {
417 - $contact = new PH_Contact( '', $current_user->ID );
418 -
419 - if ( is_array($contact->contact_types) && in_array('applicant', $contact->contact_types) )
420 - {
421 - if (
422 - $contact->applicant_profiles != '' &&
423 - $contact->applicant_profiles > 0 &&
424 - $contact->applicant_profile_0 != '' &&
425 - is_array($contact->applicant_profile_0)
426 - )
427 - {
428 - $applicant_profile = $contact->applicant_profile_0;
429 - }
430 - }
431 - }
432 - }
433 -
434 483 $fields = array();
435 484
436 485 $offices = array();
437 486 $value = '';
@@ -469,34 +518,37 @@
469 518 'value' => $value,
470 519 'options' => $offices
471 520 );
472 521
522 + $value = '';
523 +
524 + $ph_departments = ph_get_departments();
473 525 $departments = array();
474 - $value = '';
475 - 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 )
476 530 {
477 - $departments['residential-sales'] = __( 'Properties To Buy', 'propertyhive' );
478 - 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' )
479 532 {
480 - $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 + }
481 548 }
482 549 }
483 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
484 - {
485 - $departments['residential-lettings'] = __( 'Properties For Rent', 'propertyhive' );
486 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
487 - {
488 - $value = 'residential-lettings';
489 - }
490 - }
491 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
492 - {
493 - $departments['commercial'] = __( 'Commercial Properties', 'propertyhive' );
494 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'commercial')
495 - {
496 - $value = 'commercial';
497 - }
498 - }
550 +
499 551 $fields['department'] = array(
500 552 'type' => 'radio',
501 553 'label' => __( 'Looking For', 'propertyhive' ),
502 554 'required' => true,
@@ -512,9 +564,9 @@
512 564 {
513 565 $fields['department']['type'] = 'hidden';
514 566 }
515 567
516 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' || get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
568 + if ( $show_residential_fields )
517 569 {
518 570 $fields['maximum_price'] = array(
519 571 'type' => 'number',
520 572 'label' => __( 'Maximum Price', 'propertyhive' ),
@@ -554,9 +606,9 @@
554 606 $args = array(
555 607 'hide_empty' => false,
556 608 'parent' => 0
557 609 );
558 - $terms = get_terms( 'property_type', $args );
610 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
559 611
560 612 $options = array();
561 613
562 614 $selected_value = '';
@@ -561,9 +613,9 @@
561 613
562 614 $selected_value = '';
563 615 if ( !empty( $terms ) && !is_wp_error( $terms ) )
564 616 {
565 - $options = array( '' => __( 'All Property Types', 'properthive' ) );
617 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
566 618
567 619 foreach ($terms as $term)
568 620 {
569 621 $options[$term->term_id] = $term->name;
@@ -571,9 +623,9 @@
571 623 $args = array(
572 624 'hide_empty' => false,
573 625 'parent' => $term->term_id
574 626 );
575 - $subterms = get_terms( 'property_type', $args );
627 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
576 628
577 629 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
578 630 {
579 631 foreach ($subterms as $term)
@@ -590,19 +642,20 @@
590 642 'type' => 'select',
591 643 'label' => __( 'Property Type', 'propertyhive' ),
592 644 'before' => '<div class="control control-property_type residential-only">',
593 645 'required' => false,
646 + 'multiselect' => true,
594 647 'options' => $options,
595 648 );
596 649
597 650 if ( is_user_logged_in() && isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
598 651 {
599 - $fields['property_type']['value'] = $applicant_profile['property_types'][0];
652 + $fields['property_type']['value'] = $applicant_profile['property_types'];
600 653 }
601 654 }
602 655 }
603 656
604 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
657 + if ( $show_commercial_fields )
605 658 {
606 659 $fields['available_as_sale'] = array(
607 660 'type' => 'checkbox',
608 661 'label' => __( 'For Sale', 'propertyhive' ),
@@ -652,9 +705,9 @@
652 705 $args = array(
653 706 'hide_empty' => false,
654 707 'parent' => 0
655 708 );
656 - $terms = get_terms( 'commercial_property_type', $args );
709 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
657 710
658 711 $options = array();
659 712
660 713 $selected_value = '';
@@ -659,9 +712,9 @@
659 712
660 713 $selected_value = '';
661 714 if ( !empty( $terms ) && !is_wp_error( $terms ) )
662 715 {
663 - $options = array( '' => __( 'All Property Types', 'properthive' ) );
716 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
664 717
665 718 foreach ($terms as $term)
666 719 {
667 720 $options[$term->term_id] = $term->name;
@@ -669,9 +722,9 @@
669 722 $args = array(
670 723 'hide_empty' => false,
671 724 'parent' => $term->term_id
672 725 );
673 - $subterms = get_terms( 'commercial_property_type', $args );
726 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
674 727
675 728 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
676 729 {
677 730 foreach ($subterms as $term)
@@ -688,63 +741,54 @@
688 741 'type' => 'select',
689 742 'label' => __( 'Property Type', 'propertyhive' ),
690 743 'before' => '<div class="control control-commercial_property_type commercial-only">',
691 744 'required' => false,
745 + 'multiselect' => true,
692 746 'options' => $options,
693 747 );
694 748
695 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']) )
696 750 {
697 - $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'][0];
751 + $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'];
698 752 }
699 753 }
700 754 }
701 755
702 - $args = array(
703 - 'hide_empty' => false,
704 - 'parent' => 0
705 - );
706 - $terms = get_terms( 'location', $args );
707 -
708 - $options = array();
709 -
710 - $selected_value = '';
711 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
756 + if ( get_option('propertyhive_applicant_locations_type') != 'text' )
712 757 {
713 - $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' ) ) );
714 763
715 - foreach ($terms as $term)
764 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
716 765 {
717 - $options[$term->term_id] = $term->name;
718 -
719 - $args = array(
720 - 'hide_empty' => false,
721 - '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,
722 772 );
723 - $subterms = get_terms( 'location', $args );
724 773
725 - 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']) )
726 775 {
727 - foreach ($subterms as $term)
728 - {
729 - $options[$term->term_id] = '- ' . $term->name;
730 - }
776 + $fields['location']['value'] = $applicant_profile['locations'];
731 777 }
732 778 }
733 779 }
734 -
735 - if ( !empty($options) )
780 + else
736 781 {
737 - $fields['location'] = array(
738 - 'type' => 'select',
782 + $fields['location_text'] = array(
783 + 'type' => 'text',
739 784 'label' => __( 'Location', 'propertyhive' ),
740 - 'required' => false,
741 - 'options' => $options,
785 + 'required' => false
742 786 );
743 787
744 - 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'] != '' )
745 789 {
746 - $fields['location']['value'] = $applicant_profile['locations'][0];
790 + $fields['location_text']['value'] = $applicant_profile['location_text'];
747 791 }
748 792 }
749 793
750 794 $fields['additional_requirements'] = array(
@@ -764,8 +808,9 @@
764 808 * Output individual field
765 809 *
766 810 * @return void
767 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.
768 813 function ph_form_field( $key, $field )
769 814 {
770 815 global $post;
771 816
@@ -778,10 +823,11 @@
778 823 case "date":
779 824 case "number":
780 825 case "password":
781 826 {
827 + $field['id'] = isset( $field['id'] ) ? $field['id'] : $key;
782 828 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
783 - $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 ) . '">';
784 830 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
785 831 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
786 832 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
787 833 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : ( ( $field['type'] == 'date' ) ? 'dd/mm/yyyy' : '' );
@@ -788,15 +834,17 @@
788 834 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
789 835 $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
790 836
791 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.
792 839 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
793 840 {
841 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
794 842 $field['value'] = sanitize_text_field( wp_unslash( $_GET[$key] ) );
795 843 }
796 844 else
797 845 {
798 - if ( isset($post->ID) )
846 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
799 847 {
800 848 $value = get_post_meta( $post->ID, '_' . $key, true );
801 849 if ( $value != '' )
802 850 {
@@ -819,9 +867,9 @@
819 867
820 868 $output .= '<input
821 869 type="' . esc_attr( $field['type'] ) . '"
822 870 name="' . esc_attr( $key ) . '"
823 - id="' . esc_attr( $key ) . '"
871 + id="' . esc_attr( $field['id'] ) . '"
824 872 value="' . esc_attr( $field['value'] ) . '"
825 873 placeholder="' . esc_attr( $field['placeholder'] ) . '"
826 874 class="' . esc_attr( $field['class'] ) . '"
827 875 style="' . esc_attr( $field['style'] ) . '"
@@ -834,9 +882,9 @@
834 882 }
835 883 case "textarea":
836 884 {
837 885 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
838 - $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 ) . '">';
839 887 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
840 888 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
841 889 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
842 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
@@ -842,15 +890,17 @@
842 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
843 891 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
844 892
845 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.
846 895 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
847 896 {
897 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
848 898 $field['value'] = sanitize_textarea_field( wp_unslash( $_GET[$key] ) );
849 899 }
850 900 else
851 901 {
852 - if ( isset($post->ID) )
902 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
853 903 {
854 904 $value = get_post_meta( $post->ID, '_' . $key, true );
855 905 if ( $value != '' )
856 906 {
@@ -876,9 +926,9 @@
876 926 id="' . esc_attr( $key ) . '"
877 927 placeholder="' . esc_attr( $field['placeholder'] ) . '"
878 928 class="' . esc_attr( $field['class'] ) . '"
879 929 ' . ( ($field['required']) ? 'required' : '' ) . '
880 - >' . esc_attr( $field['value'] ) . '</textarea>';
930 + >' . esc_textarea( $field['value'] ) . '</textarea>';
881 931
882 932 $output .= $field['after'];
883 933
884 934 break;
@@ -885,9 +935,9 @@
885 935 }
886 936 case "checkbox":
887 937 {
888 938 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
889 - $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 ) . '">';
890 940 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
891 941 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
892 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
893 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
@@ -892,8 +942,9 @@
892 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
893 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
894 944 $field['value'] = isset( $field['value'] ) ? $field['value'] : 'yes';
895 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.
896 947 if ( isset( $_GET[$key] ) && sanitize_text_field(wp_unslash($_GET[$key])) == $field['value'] )
897 948 {
898 949 $field['checked'] = true;
899 950 }
@@ -898,9 +949,9 @@
898 949 $field['checked'] = true;
899 950 }
900 951 else
901 952 {
902 - if ( isset($post->ID) )
953 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
903 954 {
904 955 $value = get_post_meta( $post->ID, '_' . $key, true );
905 956 if ( $value == 'yes' )
906 957 {
@@ -930,16 +981,23 @@
930 981 }
931 982 case "radio":
932 983 {
933 984 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
934 - $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 ) . '">';
935 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'] : '';
936 991 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : false;
937 992 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
993 + $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
938 994
939 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.
940 997 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
941 998 {
999 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
942 1000 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
943 1001 }
944 1002
945 1003 $output .= $field['before'];
@@ -945,20 +1003,33 @@
945 1003 $output .= $field['before'];
946 1004
947 1005 if ($field['show_label'])
948 1006 {
949 - $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>';
950 1014 }
951 1015
952 1016 foreach ( $field['options'] as $option_key => $value )
953 1017 {
954 - $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
955 1022 type="' . esc_attr( $field['type'] ) . '"
956 1023 name="' . esc_attr( $key ) . '"
1024 + id="' . $id . '"
957 1025 value="' . esc_attr( $option_key ) . '"
958 1026 class="' . esc_attr( $field['class'] ) . '"
959 1027 ' . checked( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
960 - > ' . 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']);
961 1032 }
962 1033
963 1034 $output .= $field['after'];
964 1035
@@ -966,23 +1037,31 @@
966 1037 }
967 1038 case "select":
968 1039 {
969 1040 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
970 - $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 ) . '">';
971 1042 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
972 1043 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
973 1044 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
974 1045 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
975 1046 $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
1047 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
976 1048
1049 + if ( $field['multiselect'] )
1050 + {
1051 + wp_enqueue_script( 'multiselect' );
1052 + }
1053 +
977 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.
978 1056 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
979 1057 {
1058 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
980 1059 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
981 1060 }
982 1061 else
983 1062 {
984 - if ( isset($post->ID) )
1063 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
985 1064 {
986 1065 $value = get_post_meta( $post->ID, '_' . $key, true );
987 1066 if ( $value != '' )
988 1067 {
@@ -1002,20 +1081,54 @@
1002 1081 }
1003 1082 $output .= '</label>';
1004 1083 }
1005 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 +
1006 1095 $output .= '<select
1007 - name="' . esc_attr( $key ) . '"
1096 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1008 1097 id="' . esc_attr( $key ) . '"
1009 - 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) . '"
1010 1101 >';
1011 1102
1012 1103 foreach ( $field['options'] as $option_key => $value )
1013 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 +
1014 1111 $output .= '<option
1015 - value="' . esc_attr( $option_key ) . '"
1016 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
1017 - >' . 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>';
1018 1131 }
1019 1132
1020 1133 $output .= '</select>';
1021 1134
@@ -1027,16 +1140,25 @@
1027 1140 {
1028 1141 $key = 'officeID';
1029 1142
1030 1143 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1031 - $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 ) . '">';
1032 1145 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1033 1146 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1034 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;
1035 1150
1151 + if ( $field['multiselect'] )
1152 + {
1153 + wp_enqueue_script( 'multiselect' );
1154 + }
1155 +
1036 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.
1037 1158 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1038 1159 {
1160 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1039 1161 $field['value'] = (int)$_GET[$key];
1040 1162 }
1041 1163
1042 1164 $output .= $field['before'];
@@ -1046,19 +1168,24 @@
1046 1168 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1047 1169 }
1048 1170
1049 1171 $output .= '<select
1050 - name="' . esc_attr( $key ) . '"
1172 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1051 1173 id="' . esc_attr( $key ) . '"
1052 - class="' . esc_attr( $field['class'] ) . '"
1053 - >';
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 + >';
1054 1178
1055 - $output .= '<option
1179 + if ( !$field['multiselect'] )
1180 + {
1181 + $output .= '<option
1056 1182 value=""
1057 1183 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
1058 - >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
1184 + >' . esc_html( $field['blank_option'] ) . '</option>';
1185 + }
1059 1186
1060 - $args = array(
1187 + $args = array(
1061 1188 'post_type' => 'office',
1062 1189 'nopaging' => true,
1063 1190 'orderby' => 'title',
1064 1191 'order' => 'ASC'
@@ -1071,11 +1198,22 @@
1071 1198 {
1072 1199 $office_query->the_post();
1073 1200
1074 1201 $output .= '<option
1075 - value="' . esc_attr( $post->ID ) . '"
1076 - ' . selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false ) . '
1077 - >' . 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>';
1078 1216
1079 1217 }
1080 1218 }
1081 1219 wp_reset_postdata();
@@ -1088,16 +1226,18 @@
1088 1226 }
1089 1227 case "country":
1090 1228 {
1091 1229 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1092 - $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 ) . '">';
1093 1231 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1094 1232 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1095 1233 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1096 1234
1097 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.
1098 1237 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1099 1238 {
1239 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1100 1240 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1101 1241 }
1102 1242
1103 1243 $output .= $field['before'];
@@ -1142,24 +1282,152 @@
1142 1282 $output .= $field['after'];
1143 1283
1144 1284 break;
1145 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 + }
1146 1412 case "hidden":
1147 1413 {
1148 1414 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1149 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.
1150 1417 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1151 1418 {
1419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1152 1420 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1153 1421 }
1154 1422
1155 - $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']) . '">';
1156 1424 break;
1157 1425 }
1158 1426 case "html":
1159 1427 {
1160 1428 $field['html'] = isset( $field['html'] ) ? $field['html'] : '';
1161 - $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 ) . '">';
1162 1430 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1163 1431
1164 1432 $output .= $field['before'];
1165 1433 $output .= $field['html'];
@@ -1168,12 +1436,80 @@
1168 1436 break;
1169 1437 }
1170 1438 case "recaptcha":
1171 1439 {
1172 - $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' );
1173 1476
1174 - $output .= '<script src="https://www.google.com/recaptcha/api.js"></script>
1175 - <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 +
1176 1512 break;
1177 1513 }
1178 1514 default:
1179 1515 {
@@ -1179,70 +1515,200 @@
1179 1515 {
1180 1516 if ( taxonomy_exists($field['type']) )
1181 1517 {
1182 1518 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1183 - $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 ) . '">';
1184 1520 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1185 1521 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1186 1522 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1187 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
1188 1528
1189 - $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1190 - if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1529 + if ( $field['multiselect'] )
1191 1530 {
1192 - $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1531 + wp_enqueue_script( 'multiselect' );
1193 1532 }
1194 -
1195 - $output .= $field['before'];
1196 -
1197 - if ($field['show_label'])
1198 - {
1199 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1200 - }
1201 -
1202 - $output .= '<select
1203 - name="' . esc_attr( $key ) . '"
1204 - id="' . esc_attr( $key ) . '"
1205 - class="' . esc_attr( $field['class'] ) . '"
1206 - >';
1207 -
1208 - $options = array( '' => $field['blank_option'] );
1533 +
1534 + $options = array(
1535 + '' => array(
1536 + 'label' => $field['blank_option'],
1537 + 'parent' => 0
1538 + )
1539 + );
1209 1540 $args = array(
1210 - 'hide_empty' => false,
1541 + 'hide_empty' => $field['hide_empty'],
1211 1542 'parent' => 0
1212 1543 );
1213 - $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'] ) ) );
1214 1546
1215 - $selected_value = '';
1547 + $levels_of_taxonomy = 1;
1216 1548 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1217 1549 {
1218 1550 foreach ($terms as $term)
1219 1551 {
1220 - $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 + );
1221 1575
1222 - $args = array(
1223 - 'hide_empty' => false,
1224 - '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
1225 1589 );
1226 - $subterms = get_terms( $field['type'], $args );
1227 1590
1228 - 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 + )
1229 1602 {
1230 - 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 ) )
1231 1612 {
1232 - $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 + );
1233 1638
1234 - $args = array(
1235 - 'hide_empty' => false,
1236 - 'parent' => $term->term_id
1237 - );
1238 - $subsubterms = get_terms( $field['type'], $args );
1639 + $empty_check_args = apply_filters( 'propertyhive_taxonomy_hide_empty_args', $empty_check_args, $field, $subterm->term_id );
1239 1640
1240 - if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1241 - {
1242 - 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 ) )
1243 1666 {
1244 - $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 + }
1245 1711 }
1246 1712 }
1247 1713 }
1248 1714 }
@@ -1248,21 +1714,112 @@
1248 1714 }
1249 1715 }
1250 1716 }
1251 1717
1252 - foreach ( $options as $option_key => $value )
1718 + if ( $field['dynamic_population'] )
1253 1719 {
1254 - $output .= '<option
1255 - value="' . esc_attr( $option_key ) . '"
1256 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
1257 - >' . 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' );
1258 1730 }
1259 1731
1260 - $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 + }
1261 1739
1262 - $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 + }
1263 1819 }
1264 1820 }
1265 1821 }
1266 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).
1267 1824 echo $output;
1268 -}
1825 +}