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