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 +597 -290 1.4.622.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;
@@ -78,8 +88,9 @@
78 88 * Get default fields to be shown on search forms
79 89 *
80 90 * @return array
81 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.
82 93 function ph_get_search_form_fields()
83 94 {
84 95 $fields = array();
85 96
@@ -100,8 +111,68 @@
100 111 }
101 112 }
102 113 }
103 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 +
104 175 $fields['department'] = array(
105 176 'type' => 'radio',
106 177 'options' => $department_options,
107 178 'value' => $default_value
@@ -106,11 +177,11 @@
106 177 'options' => $department_options,
107 178 'value' => $default_value
108 179 );
109 180
110 - if ( array_key_exists('residential-sales', $departments) || array_key_exists('residential-lettings', $departments) )
181 + if ( $sales_department_active || $lettings_department_active )
111 182 {
112 - if ( array_key_exists('residential-sales', $departments) )
183 + if ( $sales_department_active )
113 184 {
114 185 $prices = array(
115 186 '' => __( 'No preference', 'propertyhive' ),
116 187 '100000' => '&pound;100,000',
@@ -139,9 +210,9 @@
139 210 'options' => $prices
140 211 );
141 212 }
142 213
143 - if ( array_key_exists('residential-lettings', $departments) )
214 + if ( $lettings_department_active )
144 215 {
145 216 $prices = array(
146 217 '' => __( 'No preference', 'propertyhive' ),
147 218 '500' => '&pound;500 PCM',
@@ -185,9 +256,9 @@
185 256 'label' => __( 'Type', 'propertyhive' ),
186 257 );
187 258 }
188 259
189 - if ( array_key_exists('commercial', $departments) )
260 + if ( $commercial_department_active )
190 261 {
191 262 $sizes = array(
192 263 '' => __( 'No preference', 'propertyhive' ),
193 264 '250' => '250 sq ft',
@@ -215,45 +286,13 @@
215 286 'before' => '<div class="control control-maximum_floor_area commercial-only">',
216 287 'options' => $sizes
217 288 );
218 289
219 - // Property Type
220 - $options = array( '' => __( 'No preference', 'propertyhive' ) );
221 - $args = array(
222 - 'hide_empty' => false,
223 - 'parent' => 0
224 - );
225 - $terms = get_terms( 'commercial_property_type', $args );
226 -
227 - $selected_value = '';
228 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
229 - {
230 - foreach ($terms as $term)
231 - {
232 - $options[$term->term_id] = $term->name;
233 -
234 - $args = array(
235 - 'hide_empty' => false,
236 - 'parent' => $term->term_id
237 - );
238 - $subterms = get_terms( 'commercial_property_type', $args );
239 -
240 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
241 - {
242 - foreach ($subterms as $term)
243 - {
244 - $options[$term->term_id] = '- ' . $term->name;
245 - }
246 - }
247 - }
248 - }
249 -
250 290 $fields['commercial_property_type'] = array(
251 - 'type' => 'select',
291 + 'type' => 'commercial_property_type',
252 292 'show_label' => true,
253 293 'before' => '<div class="control control-commercial_property_type commercial-only">',
254 294 'label' => __( 'Type', 'propertyhive' ),
255 - 'options' => $options
256 295 );
257 296 }
258 297
259 298 return $fields;
@@ -261,17 +300,45 @@
261 300
262 301 /**
263 302 * Main function for drawing property enquiry form.
264 303 *
265 - * @param string $id
304 + * @param string $property_id
266 305 * @return void
267 306 */
268 307 function propertyhive_enquiry_form( $property_id = '' )
269 308 {
309 + global $post;
310 +
270 311 $form_controls = ph_get_property_enquiry_form_fields( $property_id );
271 312
272 - $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 );
273 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 +
274 341 ph_get_template( 'global/make-enquiry-form.php',array( 'form_controls' => $form_controls ) );
275 342 }
276 343
277 344 /**
@@ -278,8 +345,9 @@
278 345 * Get default fields to be shown on search forms
279 346 *
280 347 * @return array
281 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.
282 350 function ph_get_property_enquiry_form_fields( $property_id = '' )
283 351 {
284 352 global $post;
285 353
@@ -284,16 +352,13 @@
284 352 global $post;
285 353
286 354 $fields = array();
287 355
288 - $fields['property_id'] = array(
289 - 'type' => 'hidden',
290 - 'value' => ( $property_id != '' ? $property_id : $post->ID )
291 - );
292 -
293 356 $fields['name'] = array(
294 357 'type' => 'text',
295 358 'label' => __( 'Full Name', 'propertyhive' ),
359 + 'show_label' => true,
360 + 'before' => '<div class="control control-name">',
296 361 'required' => true
297 362 );
298 363 if ( is_user_logged_in() )
299 364 {
@@ -304,8 +369,10 @@
304 369
305 370 $fields['email_address'] = array(
306 371 'type' => 'email',
307 372 'label' => __( 'Email Address', 'propertyhive' ),
373 + 'show_label' => true,
374 + 'before' => '<div class="control control-email_address">',
308 375 'required' => true
309 376 );
310 377 if ( is_user_logged_in() )
311 378 {
@@ -316,8 +383,10 @@
316 383
317 384 $fields['telephone_number'] = array(
318 385 'type' => 'text',
319 386 'label' => __( 'Number', 'propertyhive' ),
387 + 'show_label' => true,
388 + 'before' => '<div class="control control-telephone_number">',
320 389 'required' => true
321 390 );
322 391
323 392 $fields['message'] = array(
@@ -322,23 +391,13 @@
322 391
323 392 $fields['message'] = array(
324 393 'type' => 'textarea',
325 394 'label' => __( 'Message', 'propertyhive' ),
395 + 'show_label' => true,
396 + 'before' => '<div class="control control-message">',
326 397 'required' => true
327 398 );
328 399
329 - if ( get_option( 'propertyhive_property_enquiry_form_disclaimer', '' ) != '' )
330 - {
331 - $disclaimer = get_option( 'propertyhive_property_enquiry_form_disclaimer', '' );
332 -
333 - $fields['disclaimer'] = array(
334 - 'type' => 'checkbox',
335 - 'label' => $disclaimer,
336 - 'label_style' => 'width:100%;',
337 - 'required' => true
338 - );
339 - }
340 -
341 400 return $fields;
342 401 }
343 402
344 403 /**
@@ -345,8 +404,9 @@
345 404 * Get default fields to be shown on applicant registration forms
346 405 *
347 406 * @return array
348 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.
349 409 function ph_get_user_details_form_fields()
350 410 {
351 411 global $post;
352 412
@@ -414,36 +474,13 @@
414 474 * Get default fields to be shown on applicant registration forms
415 475 *
416 476 * @return array
417 477 */
418 -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)
419 480 {
420 481 global $post;
421 482
422 - if ( is_user_logged_in() )
423 - {
424 - $current_user = wp_get_current_user();
425 - $applicant_profile = false;
426 -
427 - if ( $current_user instanceof WP_User )
428 - {
429 - $contact = new PH_Contact( '', $current_user->ID );
430 -
431 - if ( is_array($contact->contact_types) && in_array('applicant', $contact->contact_types) )
432 - {
433 - if (
434 - $contact->applicant_profiles != '' &&
435 - $contact->applicant_profiles > 0 &&
436 - $contact->applicant_profile_0 != '' &&
437 - is_array($contact->applicant_profile_0)
438 - )
439 - {
440 - $applicant_profile = $contact->applicant_profile_0;
441 - }
442 - }
443 - }
444 - }
445 -
446 483 $fields = array();
447 484
448 485 $offices = array();
449 486 $value = '';
@@ -481,34 +518,37 @@
481 518 'value' => $value,
482 519 'options' => $offices
483 520 );
484 521
522 + $value = '';
523 +
524 + $ph_departments = ph_get_departments();
485 525 $departments = array();
486 - $value = '';
487 - 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 )
488 530 {
489 - $departments['residential-sales'] = __( 'Properties To Buy', 'propertyhive' );
490 - 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' )
491 532 {
492 - $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 + }
493 548 }
494 549 }
495 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
496 - {
497 - $departments['residential-lettings'] = __( 'Properties For Rent', 'propertyhive' );
498 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
499 - {
500 - $value = 'residential-lettings';
501 - }
502 - }
503 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
504 - {
505 - $departments['commercial'] = __( 'Commercial Properties', 'propertyhive' );
506 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'commercial')
507 - {
508 - $value = 'commercial';
509 - }
510 - }
550 +
511 551 $fields['department'] = array(
512 552 'type' => 'radio',
513 553 'label' => __( 'Looking For', 'propertyhive' ),
514 554 'required' => true,
@@ -524,9 +564,9 @@
524 564 {
525 565 $fields['department']['type'] = 'hidden';
526 566 }
527 567
528 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' || get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
568 + if ( $show_residential_fields )
529 569 {
530 570 $fields['maximum_price'] = array(
531 571 'type' => 'number',
532 572 'label' => __( 'Maximum Price', 'propertyhive' ),
@@ -566,9 +606,9 @@
566 606 $args = array(
567 607 'hide_empty' => false,
568 608 'parent' => 0
569 609 );
570 - $terms = get_terms( 'property_type', $args );
610 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
571 611
572 612 $options = array();
573 613
574 614 $selected_value = '';
@@ -583,9 +623,9 @@
583 623 $args = array(
584 624 'hide_empty' => false,
585 625 'parent' => $term->term_id
586 626 );
587 - $subterms = get_terms( 'property_type', $args );
627 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
588 628
589 629 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
590 630 {
591 631 foreach ($subterms as $term)
@@ -602,19 +642,20 @@
602 642 'type' => 'select',
603 643 'label' => __( 'Property Type', 'propertyhive' ),
604 644 'before' => '<div class="control control-property_type residential-only">',
605 645 'required' => false,
646 + 'multiselect' => true,
606 647 'options' => $options,
607 648 );
608 649
609 650 if ( is_user_logged_in() && isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
610 651 {
611 - $fields['property_type']['value'] = $applicant_profile['property_types'][0];
652 + $fields['property_type']['value'] = $applicant_profile['property_types'];
612 653 }
613 654 }
614 655 }
615 656
616 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
657 + if ( $show_commercial_fields )
617 658 {
618 659 $fields['available_as_sale'] = array(
619 660 'type' => 'checkbox',
620 661 'label' => __( 'For Sale', 'propertyhive' ),
@@ -664,9 +705,9 @@
664 705 $args = array(
665 706 'hide_empty' => false,
666 707 'parent' => 0
667 708 );
668 - $terms = get_terms( 'commercial_property_type', $args );
709 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
669 710
670 711 $options = array();
671 712
672 713 $selected_value = '';
@@ -681,9 +722,9 @@
681 722 $args = array(
682 723 'hide_empty' => false,
683 724 'parent' => $term->term_id
684 725 );
685 - $subterms = get_terms( 'commercial_property_type', $args );
726 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
686 727
687 728 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
688 729 {
689 730 foreach ($subterms as $term)
@@ -700,63 +741,54 @@
700 741 'type' => 'select',
701 742 'label' => __( 'Property Type', 'propertyhive' ),
702 743 'before' => '<div class="control control-commercial_property_type commercial-only">',
703 744 'required' => false,
745 + 'multiselect' => true,
704 746 'options' => $options,
705 747 );
706 748
707 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']) )
708 750 {
709 - $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'][0];
751 + $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'];
710 752 }
711 753 }
712 754 }
713 755
714 - $args = array(
715 - 'hide_empty' => false,
716 - 'parent' => 0
717 - );
718 - $terms = get_terms( 'location', $args );
719 -
720 - $options = array();
721 -
722 - $selected_value = '';
723 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
756 + if ( get_option('propertyhive_applicant_locations_type') != 'text' )
724 757 {
725 - $options = array( '' => __( 'All Locations', 'propertyhive' ) );
758 + $args = array(
759 + 'hide_empty' => false,
760 + 'parent' => 0
761 + );
762 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
726 763
727 - foreach ($terms as $term)
764 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
728 765 {
729 - $options[$term->term_id] = $term->name;
730 -
731 - $args = array(
732 - 'hide_empty' => false,
733 - '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,
734 772 );
735 - $subterms = get_terms( 'location', $args );
736 773
737 - 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']) )
738 775 {
739 - foreach ($subterms as $term)
740 - {
741 - $options[$term->term_id] = '- ' . $term->name;
742 - }
776 + $fields['location']['value'] = $applicant_profile['locations'];
743 777 }
744 778 }
745 779 }
746 -
747 - if ( !empty($options) )
780 + else
748 781 {
749 - $fields['location'] = array(
750 - 'type' => 'select',
782 + $fields['location_text'] = array(
783 + 'type' => 'text',
751 784 'label' => __( 'Location', 'propertyhive' ),
752 - 'required' => false,
753 - 'options' => $options,
785 + 'required' => false
754 786 );
755 787
756 - 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'] != '' )
757 789 {
758 - $fields['location']['value'] = $applicant_profile['locations'][0];
790 + $fields['location_text']['value'] = $applicant_profile['location_text'];
759 791 }
760 792 }
761 793
762 794 $fields['additional_requirements'] = array(
@@ -776,8 +808,9 @@
776 808 * Output individual field
777 809 *
778 810 * @return void
779 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.
780 813 function ph_form_field( $key, $field )
781 814 {
782 815 global $post;
783 816
@@ -790,10 +823,11 @@
790 823 case "date":
791 824 case "number":
792 825 case "password":
793 826 {
827 + $field['id'] = isset( $field['id'] ) ? $field['id'] : $key;
794 828 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
795 - $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 ) . '">';
796 830 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
797 831 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
798 832 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
799 833 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : ( ( $field['type'] == 'date' ) ? 'dd/mm/yyyy' : '' );
@@ -800,10 +834,12 @@
800 834 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
801 835 $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
802 836
803 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.
804 839 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
805 840 {
841 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
806 842 $field['value'] = sanitize_text_field( wp_unslash( $_GET[$key] ) );
807 843 }
808 844 else
809 845 {
@@ -831,9 +867,9 @@
831 867
832 868 $output .= '<input
833 869 type="' . esc_attr( $field['type'] ) . '"
834 870 name="' . esc_attr( $key ) . '"
835 - id="' . esc_attr( $key ) . '"
871 + id="' . esc_attr( $field['id'] ) . '"
836 872 value="' . esc_attr( $field['value'] ) . '"
837 873 placeholder="' . esc_attr( $field['placeholder'] ) . '"
838 874 class="' . esc_attr( $field['class'] ) . '"
839 875 style="' . esc_attr( $field['style'] ) . '"
@@ -846,9 +882,9 @@
846 882 }
847 883 case "textarea":
848 884 {
849 885 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
850 - $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 ) . '">';
851 887 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
852 888 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
853 889 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
854 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
@@ -854,10 +890,12 @@
854 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
855 891 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
856 892
857 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.
858 895 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
859 896 {
897 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
860 898 $field['value'] = sanitize_textarea_field( wp_unslash( $_GET[$key] ) );
861 899 }
862 900 else
863 901 {
@@ -888,9 +926,9 @@
888 926 id="' . esc_attr( $key ) . '"
889 927 placeholder="' . esc_attr( $field['placeholder'] ) . '"
890 928 class="' . esc_attr( $field['class'] ) . '"
891 929 ' . ( ($field['required']) ? 'required' : '' ) . '
892 - >' . esc_attr( $field['value'] ) . '</textarea>';
930 + >' . esc_textarea( $field['value'] ) . '</textarea>';
893 931
894 932 $output .= $field['after'];
895 933
896 934 break;
@@ -897,9 +935,9 @@
897 935 }
898 936 case "checkbox":
899 937 {
900 938 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
901 - $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 ) . '">';
902 940 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
903 941 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
904 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
905 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
@@ -904,8 +942,9 @@
904 942 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
905 943 $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
906 944 $field['value'] = isset( $field['value'] ) ? $field['value'] : 'yes';
907 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.
908 947 if ( isset( $_GET[$key] ) && sanitize_text_field(wp_unslash($_GET[$key])) == $field['value'] )
909 948 {
910 949 $field['checked'] = true;
911 950 }
@@ -942,16 +981,23 @@
942 981 }
943 982 case "radio":
944 983 {
945 984 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
946 - $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 ) . '">';
947 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'] : '';
948 991 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : false;
949 992 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
993 + $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
950 994
951 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.
952 997 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
953 998 {
999 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
954 1000 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
955 1001 }
956 1002
957 1003 $output .= $field['before'];
@@ -957,20 +1003,33 @@
957 1003 $output .= $field['before'];
958 1004
959 1005 if ($field['show_label'])
960 1006 {
961 - $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>';
962 1014 }
963 1015
964 1016 foreach ( $field['options'] as $option_key => $value )
965 1017 {
966 - $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
967 1022 type="' . esc_attr( $field['type'] ) . '"
968 1023 name="' . esc_attr( $key ) . '"
1024 + id="' . $id . '"
969 1025 value="' . esc_attr( $option_key ) . '"
970 1026 class="' . esc_attr( $field['class'] ) . '"
971 1027 ' . checked( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
972 - > ' . 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']);
973 1032 }
974 1033
975 1034 $output .= $field['after'];
976 1035
@@ -978,9 +1037,9 @@
978 1037 }
979 1038 case "select":
980 1039 {
981 1040 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
982 - $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 ) . '">';
983 1042 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
984 1043 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
985 1044 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
986 1045 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
@@ -992,15 +1051,17 @@
992 1051 wp_enqueue_script( 'multiselect' );
993 1052 }
994 1053
995 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.
996 1056 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
997 1057 {
1058 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
998 1059 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
999 1060 }
1000 1061 else
1001 1062 {
1002 - if ( !is_post_type_archive('property') && isset($post->ID) )
1063 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
1003 1064 {
1004 1065 $value = get_post_meta( $post->ID, '_' . $key, true );
1005 1066 if ( $value != '' )
1006 1067 {
@@ -1054,9 +1115,15 @@
1054 1115 $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1055 1116 }
1056 1117 else
1057 1118 {
1058 - 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 + )
1059 1126 {
1060 1127 $output .= ' selected';
1061 1128 }
1062 1129 }
@@ -1073,12 +1140,13 @@
1073 1140 {
1074 1141 $key = 'officeID';
1075 1142
1076 1143 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1077 - $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 ) . '">';
1078 1145 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1079 1146 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1080 1147 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1148 + $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1081 1149 $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1082 1150
1083 1151 if ( $field['multiselect'] )
1084 1152 {
@@ -1085,10 +1153,12 @@
1085 1153 wp_enqueue_script( 'multiselect' );
1086 1154 }
1087 1155
1088 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.
1089 1158 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1090 1159 {
1160 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1091 1161 $field['value'] = (int)$_GET[$key];
1092 1162 }
1093 1163
1094 1164 $output .= $field['before'];
@@ -1102,9 +1172,9 @@
1102 1172 name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1103 1173 id="' . esc_attr( $key ) . '"
1104 1174 class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1105 1175 ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1106 - data-blank-option="' . esc_attr( __( 'No preference', 'propertyhive' ) ) . '"
1176 + data-blank-option="' . esc_attr( $field['blank_option'] ) . '"
1107 1177 >';
1108 1178
1109 1179 if ( !$field['multiselect'] )
1110 1180 {
@@ -1110,9 +1180,9 @@
1110 1180 {
1111 1181 $output .= '<option
1112 1182 value=""
1113 1183 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
1114 - >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
1184 + >' . esc_html( $field['blank_option'] ) . '</option>';
1115 1185 }
1116 1186
1117 1187 $args = array(
1118 1188 'post_type' => 'office',
@@ -1135,8 +1205,9 @@
1135 1205 $output .= selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false );
1136 1206 }
1137 1207 else
1138 1208 {
1209 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1139 1210 if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($post->ID, $_REQUEST[$key]) )
1140 1211 {
1141 1212 $output .= ' selected';
1142 1213 }
@@ -1155,16 +1226,18 @@
1155 1226 }
1156 1227 case "country":
1157 1228 {
1158 1229 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1159 - $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 ) . '">';
1160 1231 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1161 1232 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1162 1233 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1163 1234
1164 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.
1165 1237 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1166 1238 {
1239 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1167 1240 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1168 1241 }
1169 1242
1170 1243 $output .= $field['before'];
@@ -1214,11 +1287,12 @@
1214 1287 {
1215 1288 wp_enqueue_script('jquery');
1216 1289 wp_enqueue_script('jquery-ui-core');
1217 1290 wp_enqueue_script('jquery-ui-slider');
1291 + wp_enqueue_script( 'jquery-touch-punch' );
1218 1292 wp_enqueue_style( 'jquery-ui-style', PH()->plugin_url() . '/assets/css/jquery-ui/jquery-ui.css', array(), PH_VERSION );
1219 1293
1220 - $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . $key . '">';
1294 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
1221 1295 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1222 1296 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1223 1297 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1224 1298 $field['min'] = isset( $field['min'] ) ? $field['min'] : '';
@@ -1229,35 +1303,23 @@
1229 1303
1230 1304 if ($field['show_label'])
1231 1305 {
1232 1306 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'];
1233 - $output .= ' - <span id="search-form-slider-value-' . $key . '" class="search-form-slider-value"></span>';
1307 + $output .= ' - <span id="search-form-slider-value-' . esc_attr( $key ) . '" class="search-form-slider-value search-form-slider-value-' . esc_attr( $key ) . '"></span>';
1234 1308 $output .= '</label>';
1235 1309 }
1236 1310
1237 - $output .= '<div id="search-form-slider-' . $key . '" class="search-form-slider" style="min-width:150px;"></div>';
1238 - switch ( $key )
1239 - {
1240 - case "price_slider":
1241 - {
1242 - $output .= '<input type="hidden" name="minimum_price" id="min_slider_value-' . $key . '" value="' . ( isset($_GET['minimum_price']) ? ph_clean($_GET['minimum_price']) : '' ) . '">';
1243 - $output .= '<input type="hidden" name="maximum_price" id="max_slider_value-' . $key . '" value="' . ( isset($_GET['maximum_price']) ? ph_clean($_GET['maximum_price']) : '' ) . '">';
1244 - break;
1245 - }
1246 - case "rent_slider":
1247 - {
1248 - $output .= '<input type="hidden" name="minimum_rent" id="min_slider_value-' . $key . '" value="' . ( isset($_GET['minimum_rent']) ? ph_clean($_GET['minimum_rent']) : '' ) . '">';
1249 - $output .= '<input type="hidden" name="maximum_rent" id="max_slider_value-' . $key . '" value="' . ( isset($_GET['maximum_rent']) ? ph_clean($_GET['maximum_rent']) : '' ) . '">';
1250 - break;
1251 - }
1252 - case "bedrooms_slider":
1253 - {
1254 - $output .= '<input type="hidden" name="minimum_bedrooms" id="min_slider_value-' . $key . '" value="' . ( isset($_GET['minimum_bedrooms']) ? ph_clean($_GET['minimum_bedrooms']) : '' ) . '">';
1255 - $output .= '<input type="hidden" name="maximum_bedrooms" id="max_slider_value-' . $key . '" value="' . ( isset($_GET['maximum_bedrooms']) ? ph_clean($_GET['maximum_bedrooms']) : '' ) . '">';
1256 - break;
1257 - }
1258 - }
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>';
1259 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 +
1260 1322 $output .= $field['after'];
1261 1323
1262 1324 $value = '';
1263 1325 $prefix = '';
@@ -1262,9 +1324,14 @@
1262 1324 $value = '';
1263 1325 $prefix = '';
1264 1326 $suffix = '';
1265 1327
1266 - if ( $key == 'price_slider' || $key == 'rent_slider' )
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) )
1267 1334 {
1268 1335 $prefix = '£';
1269 1336
1270 1337 $search_form_currency = get_option( 'propertyhive_search_form_currency', 'GBP' );
@@ -1290,52 +1357,54 @@
1290 1357 }
1291 1358 }
1292 1359 }
1293 1360
1294 - switch ( $key )
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'] != '' )
1295 1366 {
1296 - case "price_slider":
1297 - {
1298 - if ( $field['min'] != '' && $field['max'] != '' )
1299 - {
1300 - $value = 'values: [ ' . ( isset($_GET['minimum_price']) && $_GET['minimum_price'] != '' ? ph_clean($_GET['minimum_price']) : $field['min'] ) . ', ' . ( isset($_GET['maximum_price']) && $_GET['maximum_price'] != '' ? ph_clean($_GET['maximum_price']) : $field['max'] ) . ' ],';
1301 - }
1302 - break;
1303 - }
1304 - case "rent_slider":
1305 - {
1306 - if ( $field['min'] != '' && $field['max'] != '' )
1307 - {
1308 - $value = 'values: [ ' . ( isset($_GET['minimum_rent']) && $_GET['minimum_rent'] != '' ? ph_clean($_GET['minimum_rent']) : $field['min'] ) . ', ' . ( isset($_GET['maximum_rent']) && $_GET['maximum_rent'] != '' ? ph_clean($_GET['maximum_rent']) : $field['max'] ) . ' ],';
1309 - }
1310 - break;
1311 - }
1312 - case "bedrooms_slider":
1313 - {
1314 - if ( $field['min'] != '' && $field['max'] != '' )
1315 - {
1316 - $value = 'values: [ ' . ( isset($_GET['minimum_bedrooms']) && $_GET['minimum_bedrooms'] != '' ? ph_clean($_GET['minimum_bedrooms']) : $field['min'] ) . ', ' . ( isset($_GET['maximum_bedrooms']) && $_GET['maximum_bedrooms'] != '' ? ph_clean($_GET['maximum_bedrooms']) : $field['max'] ) . ' ],';
1317 - }
1318 - break;
1319 - }
1367 + $value = 'values: [ ' . ( $minimum !== '' ? (float) $minimum : (float)$field['min'] ) . ', ' . ( $maximum !== '' ? (float) $maximum : (float)$field['max'] ) . ' ],';
1320 1368 }
1321 1369
1322 1370 $output .= '<script>
1323 1371 jQuery(document).ready(function()
1324 1372 {
1325 - jQuery( "#search-form-slider-' . $key . '" ).slider({
1326 - range: ' . ( ( $field['min'] != '' && $field['max'] != '' ) ? 'true' : 'false' ) . ',
1327 - step: ' . $field['step'] . ',
1328 - ' . ( $field['min'] != '' ? 'min: ' . $field['min'] . ',' : '' ) . '
1329 - ' . ( $field['max'] != '' ? 'max: ' . $field['max'] . ',' : '' ) . '
1330 - ' . $value . '
1331 - slide: function( event, ui ) {
1332 - jQuery( "#search-form-slider-value-' . $key . '" ).html( "' . $prefix . '" + ui.values[ 0 ].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,") + "' . $suffix . '" + " - ' . $prefix . '" + ui.values[ 1 ].toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,") + "' . $suffix . '" );
1333 - jQuery( "#min_slider_value-' . $key . '" ).val( ui.values[0] );
1334 - jQuery( "#max_slider_value-' . $key . '" ).val( ui.values[1] );
1335 - }
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 + );
1336 1406 });
1337 - jQuery( "#search-form-slider-value-' . $key . '" ).html( "' . $prefix . '" + jQuery( "#search-form-slider-' . $key . '" ).slider( "values", 0 ).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,") + "' . $suffix . '" + " - ' . $prefix . '" + jQuery( "#search-form-slider-' . $key . '" ).slider( "values", 1 ).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,") + "' . $suffix . '" );
1338 1407 });
1339 1408 </script>';
1340 1409
1341 1410 break;
@@ -1343,20 +1412,22 @@
1343 1412 case "hidden":
1344 1413 {
1345 1414 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1346 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.
1347 1417 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1348 1418 {
1419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1349 1420 $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1350 1421 }
1351 1422
1352 - $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']) . '">';
1353 1424 break;
1354 1425 }
1355 1426 case "html":
1356 1427 {
1357 1428 $field['html'] = isset( $field['html'] ) ? $field['html'] : '';
1358 - $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 ) . '">';
1359 1430 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1360 1431
1361 1432 $output .= $field['before'];
1362 1433 $output .= $field['html'];
@@ -1365,12 +1436,80 @@
1365 1436 break;
1366 1437 }
1367 1438 case "recaptcha":
1368 1439 {
1369 - $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' );
1370 1476
1371 - $output .= '<script src="https://www.google.com/recaptcha/api.js"></script>
1372 - <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 +
1373 1512 break;
1374 1513 }
1375 1514 default:
1376 1515 {
@@ -1376,56 +1515,83 @@
1376 1515 {
1377 1516 if ( taxonomy_exists($field['type']) )
1378 1517 {
1379 1518 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
1380 - $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 ) . '">';
1381 1520 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
1382 1521 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
1383 1522 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
1384 1523 $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1385 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;
1386 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
1387 1528
1388 1529 if ( $field['multiselect'] )
1389 1530 {
1390 1531 wp_enqueue_script( 'multiselect' );
1391 1532 }
1392 -
1393 - $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
1394 - if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1395 - {
1396 - $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
1397 - }
1398 -
1399 - $output .= $field['before'];
1400 -
1401 - if ($field['show_label'])
1402 - {
1403 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1404 - }
1405 -
1406 - $output .= '<select
1407 - name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1408 - id="' . esc_attr( $key ) . '"
1409 - class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1410 - ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1411 - data-blank-option="' . esc_attr($field['blank_option']) . '"
1412 - >';
1413 -
1414 - $options = array( '' => $field['blank_option'] );
1533 +
1534 + $options = array(
1535 + '' => array(
1536 + 'label' => $field['blank_option'],
1537 + 'parent' => 0
1538 + )
1539 + );
1415 1540 $args = array(
1416 - 'hide_empty' => false,
1541 + 'hide_empty' => $field['hide_empty'],
1417 1542 'parent' => 0
1418 1543 );
1419 - $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'] ) ) );
1420 1546
1421 - $selected_value = '';
1547 + $levels_of_taxonomy = 1;
1422 1548 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1423 1549 {
1424 1550 foreach ($terms as $term)
1425 1551 {
1426 - $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 + );
1427 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 +
1428 1594 if (
1429 1595 !isset($field['parent_terms_only'])
1430 1596 ||
1431 1597 (
@@ -1434,30 +1600,114 @@
1434 1600 )
1435 1601 )
1436 1602 {
1437 1603 $args = array(
1438 - 'hide_empty' => false,
1439 - 'parent' => $term->term_id
1604 + 'hide_empty' => $field['hide_empty'],
1605 + 'parent' => $term->term_id,
1440 1606 );
1441 - $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'] ) ) );
1442 1610
1443 1611 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1444 1612 {
1445 - foreach ($subterms as $term)
1613 + foreach ($subterms as $subterm)
1446 1614 {
1447 - $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 + );
1448 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 +
1449 1657 $args = array(
1450 - 'hide_empty' => false,
1451 - 'parent' => $term->term_id
1658 + 'hide_empty' => $field['hide_empty'],
1659 + 'parent' => (int)$subterm->term_id
1452 1660 );
1453 - $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'] ) ) );
1454 1664
1455 1665 if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1456 1666 {
1457 - foreach ($subsubterms as $term)
1667 + foreach ($subsubterms as $subsubterm)
1458 1668 {
1459 - $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);
1460 1710 }
1461 1711 }
1462 1712 }
1463 1713 }
@@ -1464,51 +1714,107 @@
1464 1714 }
1465 1715 }
1466 1716 }
1467 1717
1468 - foreach ( $options as $option_key => $value )
1718 + if ( $field['dynamic_population'] )
1469 1719 {
1470 - 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'])
1471 1745 {
1472 - // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1473 - continue;
1746 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1474 1747 }
1475 1748
1476 - $output .= '<option
1477 - value="' . esc_attr( $option_key ) . '"';
1478 - 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 )
1479 1760 {
1480 - $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1481 - }
1482 - else
1483 - {
1484 - if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1761 + foreach ( $options as $option_key => $value )
1485 1762 {
1486 - $output .= ' selected';
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>';
1487 1793 }
1488 1794 }
1489 - $output .= '>' . esc_html( $value ) . '</option>';
1490 - }
1491 1795
1492 - $output .= '</select>';
1796 + $output .= '</select>';
1493 1797
1494 - $output .= $field['after'];
1798 + $output .= $field['after'];
1495 1799
1496 - if ( $field['type'] == 'availability' )
1497 - {
1498 - $availability_departments = get_option( 'propertyhive_availability_departments', array() );
1499 - if ( !is_array($availability_departments) ) { $availability_departments = array(); }
1800 + if ( $field['type'] == 'availability' )
1801 + {
1802 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
1803 + if ( !is_array($availability_departments) ) { $availability_departments = array(); }
1500 1804
1501 - if ( !empty($availability_departments) )
1502 - {
1805 + if ( !empty($availability_departments) )
1806 + {
1503 1807 ?>
1504 1808 <script>
1505 -var selected_availability = '<?php echo ( isset($_REQUEST[$key]) && $_REQUEST[$key] != '' ? (int)$_REQUEST[$key] : '' ); ?>';
1506 -var availability_departments = <?php echo json_encode($availability_departments); ?>;
1507 -var availabilities = <?php echo json_encode($options); ?>;
1508 -var availabilities_order = <?php echo json_encode(array_keys($options)); ?>;
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 ); ?>;
1509 1814 </script>
1510 1815 <?php
1816 + }
1511 1817 }
1512 1818 }
1513 1819 }
1514 1820 }
@@ -1513,6 +1819,7 @@
1513 1819 }
1514 1820 }
1515 1821 }
1516 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).
1517 1824 echo $output;
1518 -}
1825 +}