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 +712 -226 1.4.552.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;
@@ -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,9 +1037,9 @@
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;
@@ -982,15 +1051,17 @@
982 1051 wp_enqueue_script( 'multiselect' );
983 1052 }
984 1053
985 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.
986 1056 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
987 1057 {
1058 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
988 1059 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
989 1060 }
990 1061 else
991 1062 {
992 - if ( !is_post_type_archive('property') && isset($post->ID) )
1063 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
993 1064 {
994 1065 $value = get_post_meta( $post->ID, '_' . $key, true );
995 1066 if ( $value != '' )
996 1067 {
@@ -1044,9 +1115,15 @@
1044 1115 $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1045 1116 }
1046 1117 else
1047 1118 {
1048 - if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
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 + )
1049 1126 {
1050 1127 $output .= ' selected';
1051 1128 }
1052 1129 }
@@ -1063,16 +1140,25 @@
1063 1140 {
1064 1141 $key = 'officeID';
1065 1142
1066 1143 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1067 - $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 ) . '">';
1068 1145 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1069 1146 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1070 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;
1071 1150
1151 + if ( $field['multiselect'] )
1152 + {
1153 + wp_enqueue_script( 'multiselect' );
1154 + }
1155 +
1072 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.
1073 1158 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1074 1159 {
1160 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1075 1161 $field['value'] = (int)$_GET[$key];
1076 1162 }
1077 1163
1078 1164 $output .= $field['before'];
@@ -1082,19 +1168,24 @@
1082 1168 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1083 1169 }
1084 1170
1085 1171 $output .= '<select
1086 - name="' . esc_attr( $key ) . '"
1172 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1087 1173 id="' . esc_attr( $key ) . '"
1088 - class="' . esc_attr( $field['class'] ) . '"
1089 - >';
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 + >';
1090 1178
1091 - $output .= '<option
1179 + if ( !$field['multiselect'] )
1180 + {
1181 + $output .= '<option
1092 1182 value=""
1093 1183 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
1094 - >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
1184 + >' . esc_html( $field['blank_option'] ) . '</option>';
1185 + }
1095 1186
1096 - $args = array(
1187 + $args = array(
1097 1188 'post_type' => 'office',
1098 1189 'nopaging' => true,
1099 1190 'orderby' => 'title',
1100 1191 'order' => 'ASC'
@@ -1107,11 +1198,22 @@
1107 1198 {
1108 1199 $office_query->the_post();
1109 1200
1110 1201 $output .= '<option
1111 - value="' . esc_attr( $post->ID ) . '"
1112 - ' . selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false ) . '
1113 - >' . 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>';
1114 1216
1115 1217 }
1116 1218 }
1117 1219 wp_reset_postdata();
@@ -1124,16 +1226,18 @@
1124 1226 }
1125 1227 case "country":
1126 1228 {
1127 1229 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1128 - $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 ) . '">';
1129 1231 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1130 1232 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1131 1233 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1132 1234
1133 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.
1134 1237 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1135 1238 {
1239 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1136 1240 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1137 1241 }
1138 1242
1139 1243 $output .= $field['before'];
@@ -1178,24 +1282,152 @@
1178 1282 $output .= $field['after'];
1179 1283
1180 1284 break;
1181 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 + }
1182 1412 case "hidden":
1183 1413 {
1184 1414 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1185 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.
1186 1417 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1187 1418 {
1419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1188 1420 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1189 1421 }
1190 1422
1191 - $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']) . '">';
1192 1424 break;
1193 1425 }
1194 1426 case "html":
1195 1427 {
1196 1428 $field['html'] = isset( $field['html'] ) ? $field['html'] : '';
1197 - $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 ) . '">';
1198 1430 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1199 1431
1200 1432 $output .= $field['before'];
1201 1433 $output .= $field['html'];
@@ -1204,12 +1436,80 @@
1204 1436 break;
1205 1437 }
1206 1438 case "recaptcha":
1207 1439 {
1208 - $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' );
1209 1476
1210 - $output .= '<script src="https://www.google.com/recaptcha/api.js"></script>
1211 - <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 +
1212 1512 break;
1213 1513 }
1214 1514 default:
1215 1515 {
@@ -1215,56 +1515,83 @@
1215 1515 {
1216 1516 if ( taxonomy_exists($field['type']) )
1217 1517 {
1218 1518 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1219 - $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 ) . '">';
1220 1520 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1221 1521 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1222 1522 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1223 1523 $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1224 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;
1225 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
1226 1528
1227 1529 if ( $field['multiselect'] )
1228 1530 {
1229 1531 wp_enqueue_script( 'multiselect' );
1230 1532 }
1231 -
1232 - $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1233 - if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1234 - {
1235 - $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1236 - }
1237 -
1238 - $output .= $field['before'];
1239 -
1240 - if ($field['show_label'])
1241 - {
1242 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1243 - }
1244 -
1245 - $output .= '<select
1246 - name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1247 - id="' . esc_attr( $key ) . '"
1248 - class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1249 - ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1250 - data-blank-option="' . esc_attr($field['blank_option']) . '"
1251 - >';
1252 -
1253 - $options = array( '' => $field['blank_option'] );
1533 +
1534 + $options = array(
1535 + '' => array(
1536 + 'label' => $field['blank_option'],
1537 + 'parent' => 0
1538 + )
1539 + );
1254 1540 $args = array(
1255 - 'hide_empty' => false,
1541 + 'hide_empty' => $field['hide_empty'],
1256 1542 'parent' => 0
1257 1543 );
1258 - $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'] ) ) );
1259 1546
1260 - $selected_value = '';
1547 + $levels_of_taxonomy = 1;
1261 1548 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1262 1549 {
1263 1550 foreach ($terms as $term)
1264 1551 {
1265 - $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 + );
1266 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 +
1267 1594 if (
1268 1595 !isset($field['parent_terms_only'])
1269 1596 ||
1270 1597 (
@@ -1273,30 +1600,114 @@
1273 1600 )
1274 1601 )
1275 1602 {
1276 1603 $args = array(
1277 - 'hide_empty' => false,
1278 - 'parent' => $term->term_id
1604 + 'hide_empty' => $field['hide_empty'],
1605 + 'parent' => $term->term_id,
1279 1606 );
1280 - $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'] ) ) );
1281 1610
1282 1611 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1283 1612 {
1284 - foreach ($subterms as $term)
1613 + foreach ($subterms as $subterm)
1285 1614 {
1286 - $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 + );
1287 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 +
1288 1657 $args = array(
1289 - 'hide_empty' => false,
1290 - 'parent' => $term->term_id
1658 + 'hide_empty' => $field['hide_empty'],
1659 + 'parent' => (int)$subterm->term_id
1291 1660 );
1292 - $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'] ) ) );
1293 1664
1294 1665 if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1295 1666 {
1296 - foreach ($subsubterms as $term)
1667 + foreach ($subsubterms as $subsubterm)
1297 1668 {
1298 - $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);
1299 1710 }
1300 1711 }
1301 1712 }
1302 1713 }
@@ -1303,37 +1714,112 @@
1303 1714 }
1304 1715 }
1305 1716 }
1306 1717
1307 - foreach ( $options as $option_key => $value )
1718 + if ( $field['dynamic_population'] )
1308 1719 {
1309 - if ( $field['multiselect'] && $option_key == '' )
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' );
1730 + }
1731 +
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 + }
1739 +
1740 + for ( $level_i = 1; $level_i <= $levels_of_taxonomy; ++$level_i )
1741 + {
1742 + $output .= $field['before'];
1743 +
1744 + if ($field['show_label'])
1310 1745 {
1311 - // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1312 - continue;
1746 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1313 1747 }
1314 1748
1315 - $output .= '<option
1316 - value="' . esc_attr( $option_key ) . '"';
1317 - if ( !$field['multiselect'] )
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 )
1318 1760 {
1319 - $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
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 + }
1320 1794 }
1321 - else
1795 +
1796 + $output .= '</select>';
1797 +
1798 + $output .= $field['after'];
1799 +
1800 + if ( $field['type'] == 'availability' )
1322 1801 {
1323 - if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1802 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
1803 + if ( !is_array($availability_departments) ) { $availability_departments = array(); }
1804 +
1805 + if ( !empty($availability_departments) )
1324 1806 {
1325 - $output .= ' selected';
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
1326 1816 }
1327 1817 }
1328 - $output .= '>' . esc_html( $value ) . '</option>';
1329 1818 }
1330 -
1331 - $output .= '</select>';
1332 -
1333 - $output .= $field['after'];
1334 1819 }
1335 1820 }
1336 1821 }
1337 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).
1338 1824 echo $output;
1339 -}
1825 +}