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 +1187 -417 1.4.52.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,13 +24,15 @@
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 +
24 33 $form_controls = apply_filters( 'propertyhive_search_form_fields_' . $id, $form_controls );
34 + $form_controls = apply_filters( 'propertyhive_search_form_fields', $form_controls );
25 35
26 36 // We 100% need department so make sure it exists. If it doesn't, set a hidden field
27 37 if ( !isset($form_controls['department']) )
28 38 {
@@ -31,9 +41,46 @@
31 41 $original_department['type'] = 'hidden';
32 42
33 43 $form_controls['department'] = $original_department;
34 44 }
35 -
45 +
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.
48 + foreach ( $_REQUEST as $key => $value )
49 + {
50 + if ( isset($form_controls[$key]) )
51 + continue;
52 +
53 + if ( $key == 'officeID' && isset($form_controls['office']) )
54 + continue;
55 +
56 + if ( $key == 'paged' )
57 + continue;
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 +
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
67 + if ( is_array($value) )
68 + {
69 + foreach ( $value as $i => $val )
70 + {
71 + $form_controls[$key . '-' . $i] = array('type' => 'hidden', 'name' => $key . '[]', 'value' => stripslashes( ph_clean( $val) ));
72 + }
73 + }
74 + else
75 + {
76 + $form_controls[$key] = array('type' => 'hidden', 'value' => stripslashes( ph_clean( $value) ));
77 + }
78 + }
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 +
36 83 ph_get_template( 'global/search-form.php', array( 'form_controls' => $form_controls, 'id' => $id ) );
37 84
38 85 }
39 86
@@ -41,48 +88,100 @@
41 88 * Get default fields to be shown on search forms
42 89 *
43 90 * @return array
44 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.
45 93 function ph_get_search_form_fields()
46 94 {
47 95 $fields = array();
48 -
49 - $departments = array();
50 - $value = '';
51 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' )
96 +
97 + $departments = ph_get_departments();
98 +
99 + $department_options = array();
100 + $default_value = '';
101 +
102 + foreach ( $departments as $key => $value )
52 103 {
53 - $departments['residential-sales'] = __( 'Sales', 'propertyhive' );
54 - if ($value == '' && (get_option( 'propertyhive_primary_department' ) == 'residential-sales' || get_option( 'propertyhive_primary_department' ) === FALSE) )
104 + if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
55 105 {
56 - $value = 'residential-sales';
106 + $department_options[$key] = $value;
107 +
108 + if ($default_value == '' && get_option( 'propertyhive_primary_department' ) == $key )
109 + {
110 + $default_value = $key;
111 + }
57 112 }
58 113 }
59 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
114 +
115 + $sales_department_active = false;
116 + if ( array_key_exists('residential-sales', $departments) )
60 117 {
61 - $departments['residential-lettings'] = __( 'Lettings', 'propertyhive' );
62 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
118 + $sales_department_active = true;
119 + }
120 + else
121 + {
122 + $custom_departments = ph_get_custom_departments();
123 + if ( !empty($custom_departments) )
63 124 {
64 - $value = 'residential-lettings';
125 + foreach ( $custom_departments as $key => $department )
126 + {
127 + if ( isset($department['based_on']) && $department['based_on'] == 'residential-sales' )
128 + {
129 + $sales_department_active = true;
130 + }
131 + }
65 132 }
66 133 }
67 - if ( get_option( 'propertyhive_active_departments_commercial' ) == 'yes' )
134 +
135 + $lettings_department_active = false;
136 + if ( array_key_exists('residential-lettings', $departments) )
68 137 {
69 - $departments['commercial'] = __( 'Commercial', 'propertyhive' );
70 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'commercial')
138 + $lettings_department_active = true;
139 + }
140 + else
141 + {
142 + $custom_departments = ph_get_custom_departments();
143 + if ( !empty($custom_departments) )
71 144 {
72 - $value = 'commercial';
145 + foreach ( $custom_departments as $key => $department )
146 + {
147 + if ( isset($department['based_on']) && $department['based_on'] == 'residential-lettings' )
148 + {
149 + $lettings_department_active = true;
150 + }
151 + }
73 152 }
74 153 }
75 -
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 +
76 175 $fields['department'] = array(
77 176 'type' => 'radio',
78 - 'options' => $departments,
79 - 'value' => $value
177 + 'options' => $department_options,
178 + 'value' => $default_value
80 179 );
81 -
82 - if ( array_key_exists('residential-sales', $departments) || array_key_exists('residential-lettings', $departments) )
180 +
181 + if ( $sales_department_active || $lettings_department_active )
83 182 {
84 - if ( array_key_exists('residential-sales', $departments) )
183 + if ( $sales_department_active )
85 184 {
86 185 $prices = array(
87 186 '' => __( 'No preference', 'propertyhive' ),
88 187 '100000' => '&pound;100,000',
@@ -93,27 +192,27 @@
93 192 '500000' => '&pound;500,000',
94 193 '750000' => '&pound;750,000',
95 194 '1000000' => '&pound;1,000,000'
96 195 );
97 -
196 +
98 197 $fields['minimum_price'] = array(
99 198 'type' => 'select',
100 - 'show_label' => true,
199 + 'show_label' => true,
101 200 'label' => __( 'Min Price', 'propertyhive' ),
102 201 'before' => '<div class="control control-minimum_price sales-only">',
103 202 'options' => $prices
104 203 );
105 -
204 +
106 205 $fields['maximum_price'] = array(
107 206 'type' => 'select',
108 - 'show_label' => true,
207 + 'show_label' => true,
109 208 'label' => __( 'Max Price', 'propertyhive' ),
110 209 'before' => '<div class="control control-maximum_price sales-only">',
111 210 'options' => $prices
112 211 );
113 212 }
114 -
115 - if ( array_key_exists('residential-lettings', $departments) )
213 +
214 + if ( $lettings_department_active )
116 215 {
117 216 $prices = array(
118 217 '' => __( 'No preference', 'propertyhive' ),
119 218 '500' => '&pound;500 PCM',
@@ -123,20 +222,20 @@
123 222 '1250' => '&pound;1250 PCM',
124 223 '1500' => '&pound;1500 PCM',
125 224 '2000' => '&pound;2000 PCM'
126 225 );
127 -
226 +
128 227 $fields['minimum_rent'] = array(
129 228 'type' => 'select',
130 - 'show_label' => true,
229 + 'show_label' => true,
131 230 'label' => __( 'Min Rent', 'propertyhive' ),
132 231 'before' => '<div class="control control-minimum_rent lettings-only">',
133 232 'options' => $prices
134 233 );
135 -
234 +
136 235 $fields['maximum_rent'] = array(
137 236 'type' => 'select',
138 - 'show_label' => true,
237 + 'show_label' => true,
139 238 'label' => __( 'Max Rent', 'propertyhive' ),
140 239 'before' => '<div class="control control-maximum_rent lettings-only">',
141 240 'options' => $prices
142 241 );
@@ -143,23 +242,23 @@
143 242 }
144 243
145 244 $fields['minimum_bedrooms'] = array(
146 245 'type' => 'select',
147 - 'show_label' => true,
246 + 'show_label' => true,
148 247 'label' => __( 'Min Beds', 'propertyhive' ),
149 248 'before' => '<div class="control control-minimum_bedrooms residential-only">',
150 249 'options' => array( '' => __( 'No preference', 'propertyhive' ), 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5)
151 250 );
152 -
251 +
153 252 $fields['property_type'] = array(
154 253 'type' => 'property_type',
155 - 'show_label' => true,
254 + 'show_label' => true,
156 255 'before' => '<div class="control control-property_type residential-only">',
157 256 'label' => __( 'Type', 'propertyhive' ),
158 257 );
159 258 }
160 259
161 - if ( array_key_exists('commercial', $departments) )
260 + if ( $commercial_department_active )
162 261 {
163 262 $sizes = array(
164 263 '' => __( 'No preference', 'propertyhive' ),
165 264 '250' => '250 sq ft',
@@ -170,65 +269,33 @@
170 269 '10000' => '10,000 sq ft',
171 270 '25000' => '25,000 sq ft',
172 271 '50000' => '50,000 sq ft'
173 272 );
174 -
273 +
175 274 $fields['minimum_floor_area'] = array(
176 275 'type' => 'select',
177 - 'show_label' => true,
276 + 'show_label' => true,
178 277 'label' => __( 'Min Floor Area', 'propertyhive' ),
179 278 'before' => '<div class="control control-minimum_floor_area commercial-only">',
180 279 'options' => $sizes
181 280 );
182 -
281 +
183 282 $fields['maximum_floor_area'] = array(
184 283 'type' => 'select',
185 - 'show_label' => true,
284 + 'show_label' => true,
186 285 'label' => __( 'Max Floor Area', 'propertyhive' ),
187 286 'before' => '<div class="control control-maximum_floor_area commercial-only">',
188 287 'options' => $sizes
189 288 );
190 289
191 - // Property Type
192 - $options = array( '' => __( 'No preference', 'propertyhive' ) );
193 - $args = array(
194 - 'hide_empty' => false,
195 - 'parent' => 0
196 - );
197 - $terms = get_terms( 'commercial_property_type', $args );
198 -
199 - $selected_value = '';
200 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
201 - {
202 - foreach ($terms as $term)
203 - {
204 - $options[$term->term_id] = $term->name;
205 -
206 - $args = array(
207 - 'hide_empty' => false,
208 - 'parent' => $term->term_id
209 - );
210 - $subterms = get_terms( 'commercial_property_type', $args );
211 -
212 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
213 - {
214 - foreach ($subterms as $term)
215 - {
216 - $options[$term->term_id] = '- ' . $term->name;
217 - }
218 - }
219 - }
220 - }
221 -
222 290 $fields['commercial_property_type'] = array(
223 - 'type' => 'select',
224 - 'show_label' => true,
291 + 'type' => 'commercial_property_type',
292 + 'show_label' => true,
225 293 'before' => '<div class="control control-commercial_property_type commercial-only">',
226 294 'label' => __( 'Type', 'propertyhive' ),
227 - 'options' => $options
228 295 );
229 296 }
230 -
297 +
231 298 return $fields;
232 299 }
233 300
234 301 /**
@@ -233,17 +300,45 @@
233 300
234 301 /**
235 302 * Main function for drawing property enquiry form.
236 303 *
237 - * @param string $id
304 + * @param string $property_id
238 305 * @return void
239 306 */
240 -function propertyhive_enquiry_form()
307 +function propertyhive_enquiry_form( $property_id = '' )
241 308 {
242 - $form_controls = ph_get_property_enquiry_form_fields();
309 + global $post;
243 310
244 - $form_controls = apply_filters( 'propertyhive_property_enquiry_form_fields', $form_controls );
245 -
311 + $form_controls = ph_get_property_enquiry_form_fields( $property_id );
312 +
313 + $form_controls = apply_filters( 'propertyhive_property_enquiry_form_fields', $form_controls, $property_id );
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 +
246 341 ph_get_template( 'global/make-enquiry-form.php',array( 'form_controls' => $form_controls ) );
247 342 }
248 343
249 344 /**
@@ -250,22 +345,20 @@
250 345 * Get default fields to be shown on search forms
251 346 *
252 347 * @return array
253 348 */
254 -function ph_get_property_enquiry_form_fields()
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.
350 +function ph_get_property_enquiry_form_fields( $property_id = '' )
255 351 {
256 352 global $post;
257 -
353 +
258 354 $fields = array();
259 -
260 - $fields['property_id'] = array(
261 - 'type' => 'hidden',
262 - 'value' => $post->ID
263 - );
264 -
355 +
265 356 $fields['name'] = array(
266 357 'type' => 'text',
267 358 'label' => __( 'Full Name', 'propertyhive' ),
359 + 'show_label' => true,
360 + 'before' => '<div class="control control-name">',
268 361 'required' => true
269 362 );
270 363 if ( is_user_logged_in() )
271 364 {
@@ -272,12 +365,14 @@
272 365 $current_user = wp_get_current_user();
273 366
274 367 $fields['name']['value'] = $current_user->display_name;
275 368 }
276 -
369 +
277 370 $fields['email_address'] = array(
278 371 'type' => 'email',
279 372 'label' => __( 'Email Address', 'propertyhive' ),
373 + 'show_label' => true,
374 + 'before' => '<div class="control control-email_address">',
280 375 'required' => true
281 376 );
282 377 if ( is_user_logged_in() )
283 378 {
@@ -284,21 +379,25 @@
284 379 $current_user = wp_get_current_user();
285 380
286 381 $fields['email_address']['value'] = $current_user->user_email;
287 382 }
288 -
383 +
289 384 $fields['telephone_number'] = array(
290 385 'type' => 'text',
291 386 'label' => __( 'Number', 'propertyhive' ),
387 + 'show_label' => true,
388 + 'before' => '<div class="control control-telephone_number">',
292 389 'required' => true
293 390 );
294 -
391 +
295 392 $fields['message'] = array(
296 393 'type' => 'textarea',
297 394 'label' => __( 'Message', 'propertyhive' ),
395 + 'show_label' => true,
396 + 'before' => '<div class="control control-message">',
298 397 'required' => true
299 398 );
300 -
399 +
301 400 return $fields;
302 401 }
303 402
304 403 /**
@@ -305,8 +404,9 @@
305 404 * Get default fields to be shown on applicant registration forms
306 405 *
307 406 * @return array
308 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.
309 409 function ph_get_user_details_form_fields()
310 410 {
311 411 global $post;
312 412
@@ -318,11 +418,11 @@
318 418 {
319 419 $contact = new PH_Contact( '', $current_user->ID );
320 420 }
321 421 }
322 -
422 +
323 423 $fields = array();
324 -
424 +
325 425 $fields['name'] = array(
326 426 'type' => 'text',
327 427 'label' => __( 'Full Name', 'propertyhive' ),
328 428 'required' => true
@@ -330,9 +430,9 @@
330 430 if ( is_user_logged_in() && $current_user instanceof WP_User )
331 431 {
332 432 $fields['name']['value'] = $current_user->display_name;
333 433 }
334 -
434 +
335 435 $fields['email_address'] = array(
336 436 'type' => 'email',
337 437 'label' => __( 'Email Address', 'propertyhive' ),
338 438 'required' => true
@@ -365,9 +465,9 @@
365 465 'label' => __( 'Confirm Password', 'propertyhive' ),
366 466 'required' => true
367 467 );
368 468 }
369 -
469 +
370 470 return $fields;
371 471 }
372 472
373 473 /**
@@ -374,72 +474,14 @@
374 474 * Get default fields to be shown on applicant registration forms
375 475 *
376 476 * @return array
377 477 */
378 -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)
379 480 {
380 481 global $post;
381 482
382 - if ( is_user_logged_in() )
383 - {
384 - $current_user = wp_get_current_user();
385 - $applicant_profile = false;
386 -
387 - if ( $current_user instanceof WP_User )
388 - {
389 - $contact = new PH_Contact( '', $current_user->ID );
390 -
391 - if ( is_array($contact->contact_types) && in_array('applicant', $contact->contact_types) )
392 - {
393 - if (
394 - $contact->applicant_profiles != '' &&
395 - $contact->applicant_profiles > 0 &&
396 - $contact->applicant_profile_0 != '' &&
397 - is_array($contact->applicant_profile_0)
398 - )
399 - {
400 - $applicant_profile = $contact->applicant_profile_0;
401 - }
402 - }
403 - }
404 - }
405 -
406 483 $fields = array();
407 -
408 - $departments = array();
409 - $value = '';
410 - if ( get_option( 'propertyhive_active_departments_sales' ) == 'yes' )
411 - {
412 - $departments['residential-sales'] = __( 'Buy', 'propertyhive' );
413 - if ($value == '' && (get_option( 'propertyhive_primary_department' ) == 'residential-sales' || get_option( 'propertyhive_primary_department' ) === FALSE) )
414 - {
415 - $value = 'residential-sales';
416 - }
417 - }
418 - if ( get_option( 'propertyhive_active_departments_lettings' ) == 'yes' )
419 - {
420 - $departments['residential-lettings'] = __( 'Rent', 'propertyhive' );
421 - if ($value == '' && get_option( 'propertyhive_primary_department' ) == 'residential-lettings')
422 - {
423 - $value = 'residential-lettings';
424 - }
425 - }
426 - $fields['department'] = array(
427 - 'type' => 'radio',
428 - 'label' => __( 'Looking To', 'propertyhive' ),
429 - 'required' => true,
430 - 'show_label' => true,
431 - 'value' => $value,
432 - 'options' => $departments
433 - );
434 - if ( is_user_logged_in() && isset($applicant_profile['department']) )
435 - {
436 - $fields['department']['value'] = $applicant_profile['department'];
437 - }
438 - if ( count($departments) == 1 )
439 - {
440 - $fields['department']['type'] = 'hidden';
441 - }
442 484
443 485 $offices = array();
444 486 $value = '';
445 487
@@ -476,139 +518,280 @@
476 518 'value' => $value,
477 519 'options' => $offices
478 520 );
479 521
480 - $fields['maximum_price'] = array(
481 - 'type' => 'number',
482 - 'label' => __( 'Maximum Price', 'propertyhive' ),
483 - 'style' => 'max-width:150px;',
484 - 'before' => '<div class="control control-minimum_price sales-only">',
485 - 'required' => false
486 - );
487 - if ( is_user_logged_in() && isset($applicant_profile['max_price']) )
522 + $value = '';
523 +
524 + $ph_departments = ph_get_departments();
525 + $departments = array();
526 +
527 + $show_residential_fields = false;
528 + $show_commercial_fields = false;
529 + foreach ( $ph_departments as $key => $department )
488 530 {
489 - $fields['maximum_price']['value'] = $applicant_profile['max_price'];
531 + if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
532 + {
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 + }
548 + }
490 549 }
491 550
492 - $fields['maximum_rent'] = array(
493 - 'type' => 'number',
494 - 'label' => __( 'Maximum Rent', 'propertyhive' ) . ' (PCM)',
495 - 'style' => 'max-width:150px;',
496 - 'before' => '<div class="control control-minimum_price lettings-only">',
497 - 'required' => false
551 + $fields['department'] = array(
552 + 'type' => 'radio',
553 + 'label' => __( 'Looking For', 'propertyhive' ),
554 + 'required' => true,
555 + 'show_label' => true,
556 + 'value' => $value,
557 + 'options' => $departments
498 558 );
499 - if ( is_user_logged_in() && isset($applicant_profile['max_rent']) )
559 + if ( is_user_logged_in() && isset($applicant_profile['department']) )
500 560 {
501 - $fields['maximum_rent']['value'] = $applicant_profile['max_rent'];
561 + $fields['department']['value'] = $applicant_profile['department'];
502 562 }
563 + if ( count($departments) == 1 )
564 + {
565 + $fields['department']['type'] = 'hidden';
566 + }
503 567
504 - $fields['minimum_bedrooms'] = array(
505 - 'type' => 'number',
506 - 'label' => __( 'Minimum Bedrooms', 'propertyhive' ),
507 - 'style' => 'max-width:80px;',
508 - 'required' => false
509 - );
510 - if ( is_user_logged_in() && isset($applicant_profile['min_beds']) )
568 + if ( $show_residential_fields )
511 569 {
512 - $fields['minimum_bedrooms']['value'] = $applicant_profile['min_beds'];
513 - }
570 + $fields['maximum_price'] = array(
571 + 'type' => 'number',
572 + 'label' => __( 'Maximum Price', 'propertyhive' ),
573 + 'style' => 'max-width:150px;',
574 + 'before' => '<div class="control control-minimum_price sales-only">',
575 + 'required' => false
576 + );
577 + if ( is_user_logged_in() && isset($applicant_profile['max_price']) )
578 + {
579 + $fields['maximum_price']['value'] = $applicant_profile['max_price'];
580 + }
514 581
515 - $args = array(
516 - 'hide_empty' => false,
517 - 'parent' => 0
518 - );
519 - $terms = get_terms( 'property_type', $args );
582 + $fields['maximum_rent'] = array(
583 + 'type' => 'number',
584 + 'label' => __( 'Maximum Rent', 'propertyhive' ) . ' (PCM)',
585 + 'style' => 'max-width:150px;',
586 + 'before' => '<div class="control control-minimum_price lettings-only">',
587 + 'required' => false
588 + );
589 + if ( is_user_logged_in() && isset($applicant_profile['max_rent']) )
590 + {
591 + $fields['maximum_rent']['value'] = $applicant_profile['max_rent'];
592 + }
520 593
521 - $options = array();
594 + $fields['minimum_bedrooms'] = array(
595 + 'type' => 'number',
596 + 'label' => __( 'Minimum Bedrooms', 'propertyhive' ),
597 + 'style' => 'max-width:80px;',
598 + 'before' => '<div class="control control-minimum_bedrooms residential-only">',
599 + 'required' => false
600 + );
601 + if ( is_user_logged_in() && isset($applicant_profile['min_beds']) )
602 + {
603 + $fields['minimum_bedrooms']['value'] = $applicant_profile['min_beds'];
604 + }
522 605
523 - $selected_value = '';
524 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
525 - {
526 - $options = array( '' => __( 'All Property Types', 'properthive' ) );
606 + $args = array(
607 + 'hide_empty' => false,
608 + 'parent' => 0
609 + );
610 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
527 611
528 - foreach ($terms as $term)
612 + $options = array();
613 +
614 + $selected_value = '';
615 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
529 616 {
530 - $options[$term->term_id] = $term->name;
531 -
532 - $args = array(
533 - 'hide_empty' => false,
534 - 'parent' => $term->term_id
535 - );
536 - $subterms = get_terms( 'property_type', $args );
537 -
538 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
617 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
618 +
619 + foreach ($terms as $term)
539 620 {
540 - foreach ($subterms as $term)
621 + $options[$term->term_id] = $term->name;
622 +
623 + $args = array(
624 + 'hide_empty' => false,
625 + 'parent' => $term->term_id
626 + );
627 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'property_type' ) ) );
628 +
629 + if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
541 630 {
542 - $options[$term->term_id] = '- ' . $term->name;
631 + foreach ($subterms as $term)
632 + {
633 + $options[$term->term_id] = '- ' . $term->name;
634 + }
543 635 }
544 636 }
545 637 }
638 +
639 + if ( !empty($options) )
640 + {
641 + $fields['property_type'] = array(
642 + 'type' => 'select',
643 + 'label' => __( 'Property Type', 'propertyhive' ),
644 + 'before' => '<div class="control control-property_type residential-only">',
645 + 'required' => false,
646 + 'multiselect' => true,
647 + 'options' => $options,
648 + );
649 +
650 + if ( is_user_logged_in() && isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
651 + {
652 + $fields['property_type']['value'] = $applicant_profile['property_types'];
653 + }
654 + }
546 655 }
547 656
548 - if ( !empty($options) )
657 + if ( $show_commercial_fields )
549 658 {
550 - $fields['property_type'] = array(
551 - 'type' => 'select',
552 - 'label' => __( 'Property Type', 'propertyhive' ),
659 + $fields['available_as_sale'] = array(
660 + 'type' => 'checkbox',
661 + 'label' => __( 'For Sale', 'propertyhive' ),
662 + 'before' => '<div class="control control-available_as_sale commercial-only">',
553 663 'required' => false,
554 - 'options' => $options,
555 664 );
665 + if ( is_user_logged_in() && isset($applicant_profile['available_as']) && in_array('sale', $applicant_profile['available_as']) )
666 + {
667 + $fields['available_as_sale']['checked'] = true;
668 + }
556 669
557 - if ( is_user_logged_in() && isset($applicant_profile['property_types']) && is_array($applicant_profile['property_types']) && !empty($applicant_profile['property_types']) )
670 + $fields['available_as_rent'] = array(
671 + 'type' => 'checkbox',
672 + 'label' => __( 'To Rent', 'propertyhive' ),
673 + 'before' => '<div class="control control-available_as_rent commercial-only">',
674 + 'required' => false,
675 + );
676 + if ( is_user_logged_in() && isset($applicant_profile['available_as']) && in_array('rent', $applicant_profile['available_as']) )
558 677 {
559 - $fields['property_type']['value'] = $applicant_profile['property_types'][0];
678 + $fields['available_as_rent']['checked'] = true;
560 679 }
561 - }
562 680
563 - $args = array(
564 - 'hide_empty' => false,
565 - 'parent' => 0
566 - );
567 - $terms = get_terms( 'location', $args );
681 + $fields['minimum_floor_area'] = array(
682 + 'type' => 'number',
683 + 'label' => __( 'Min Floor Area (Sq Ft)', 'propertyhive' ),
684 + 'style' => 'max-width:150px;',
685 + 'before' => '<div class="control control-minimum_floor_area commercial-only">',
686 + 'required' => false
687 + );
688 + if ( is_user_logged_in() && isset($applicant_profile['min_floor_area']) )
689 + {
690 + $fields['minimum_floor_area']['value'] = $applicant_profile['min_floor_area'];
691 + }
568 692
569 - $options = array();
693 + $fields['maximum_floor_area'] = array(
694 + 'type' => 'number',
695 + 'label' => __( 'Max Floor Area (Sq Ft)', 'propertyhive' ),
696 + 'style' => 'max-width:150px;',
697 + 'before' => '<div class="control control-maximum_floor_area commercial-only">',
698 + 'required' => false
699 + );
700 + if ( is_user_logged_in() && isset($applicant_profile['max_floor_area']) )
701 + {
702 + $fields['maximum_floor_area']['value'] = $applicant_profile['max_floor_area'];
703 + }
570 704
571 - $selected_value = '';
572 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
573 - {
574 - $options = array( '' => __( 'All Locations', 'properthive' ) );
705 + $args = array(
706 + 'hide_empty' => false,
707 + 'parent' => 0
708 + );
709 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
575 710
576 - foreach ($terms as $term)
711 + $options = array();
712 +
713 + $selected_value = '';
714 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
577 715 {
578 - $options[$term->term_id] = $term->name;
579 -
580 - $args = array(
581 - 'hide_empty' => false,
582 - 'parent' => $term->term_id
583 - );
584 - $subterms = get_terms( 'location', $args );
585 -
586 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
716 + $options = array( '' => __( 'All Property Types', 'propertyhive' ) );
717 +
718 + foreach ($terms as $term)
587 719 {
588 - foreach ($subterms as $term)
720 + $options[$term->term_id] = $term->name;
721 +
722 + $args = array(
723 + 'hide_empty' => false,
724 + 'parent' => $term->term_id
725 + );
726 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'commercial_property_type' ) ) );
727 +
728 + if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
589 729 {
590 - $options[$term->term_id] = '- ' . $term->name;
730 + foreach ($subterms as $term)
731 + {
732 + $options[$term->term_id] = '- ' . $term->name;
733 + }
591 734 }
592 735 }
593 736 }
737 +
738 + if ( !empty($options) )
739 + {
740 + $fields['commercial_property_type'] = array(
741 + 'type' => 'select',
742 + 'label' => __( 'Property Type', 'propertyhive' ),
743 + 'before' => '<div class="control control-commercial_property_type commercial-only">',
744 + 'required' => false,
745 + 'multiselect' => true,
746 + 'options' => $options,
747 + );
748 +
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']) )
750 + {
751 + $fields['commercial_property_type']['value'] = $applicant_profile['commercial_property_types'];
752 + }
753 + }
594 754 }
595 755
596 - if ( !empty($options) )
756 + if ( get_option('propertyhive_applicant_locations_type') != 'text' )
597 757 {
598 - $fields['location'] = array(
599 - 'type' => 'select',
758 + $args = array(
759 + 'hide_empty' => false,
760 + 'parent' => 0
761 + );
762 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
763 +
764 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
765 + {
766 + $fields['location'] = array(
767 + 'type' => 'location',
768 + 'label' => __( 'Location', 'propertyhive' ),
769 + 'blank_option' => __( 'All Locations', 'propertyhive' ),
770 + 'required' => false,
771 + 'multiselect' => true,
772 + );
773 +
774 + if ( is_user_logged_in() && isset($applicant_profile['locations']) && is_array($applicant_profile['locations']) && !empty($applicant_profile['locations']) )
775 + {
776 + $fields['location']['value'] = $applicant_profile['locations'];
777 + }
778 + }
779 + }
780 + else
781 + {
782 + $fields['location_text'] = array(
783 + 'type' => 'text',
600 784 'label' => __( 'Location', 'propertyhive' ),
601 - 'required' => false,
602 - 'options' => $options,
785 + 'required' => false
603 786 );
604 787
605 - 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'] != '' )
606 789 {
607 - $fields['location']['value'] = $applicant_profile['locations'][0];
790 + $fields['location_text']['value'] = $applicant_profile['location_text'];
608 791 }
609 792 }
610 -
793 +
611 794 $fields['additional_requirements'] = array(
612 795 'type' => 'textarea',
613 796 'label' => __( 'Additional Requirements', 'propertyhive' ),
614 797 'required' => false
@@ -625,14 +808,15 @@
625 808 * Output individual field
626 809 *
627 810 * @return void
628 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.
629 813 function ph_form_field( $key, $field )
630 814 {
631 815 global $post;
632 816
633 817 $output = '';
634 -
818 +
635 819 switch ($field['type'])
636 820 {
637 821 case "text":
638 822 case "email":
@@ -639,10 +823,11 @@
639 823 case "date":
640 824 case "number":
641 825 case "password":
642 826 {
827 + $field['id'] = isset( $field['id'] ) ? $field['id'] : $key;
643 828 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
644 - $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 ) . '">';
645 830 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
646 831 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
647 832 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
648 833 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : ( ( $field['type'] == 'date' ) ? 'dd/mm/yyyy' : '' );
@@ -647,17 +832,19 @@
647 832 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
648 833 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : ( ( $field['type'] == 'date' ) ? 'dd/mm/yyyy' : '' );
649 834 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
650 835 $field['style'] = isset( $field['style'] ) ? $field['style'] : '';
651 -
836 +
652 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.
653 839 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
654 840 {
655 - $field['value'] = $_GET[$key];
841 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
842 + $field['value'] = sanitize_text_field( wp_unslash( $_GET[$key] ) );
656 843 }
657 844 else
658 845 {
659 - if ( isset($post->ID) )
846 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
660 847 {
661 848 $value = get_post_meta( $post->ID, '_' . $key, true );
662 849 if ( $value != '' )
663 850 {
@@ -664,11 +851,11 @@
664 851 $field['value'] = $value;
665 852 }
666 853 }
667 854 }
668 -
855 +
669 856 $output .= $field['before'];
670 -
857 +
671 858 if ($field['show_label'])
672 859 {
673 860 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'];
674 861 if ($field['required'])
@@ -676,42 +863,55 @@
676 863 $output .= '<span class="required"> *</span>';
677 864 }
678 865 $output .= '</label>';
679 866 }
680 -
681 - $output .= '<input
682 - type="' . esc_attr( $field['type'] ) . '"
683 - name="' . esc_attr( $key ) . '"
684 - id="' . esc_attr( $key ) . '"
685 - value="' . esc_attr( $field['value'] ) . '"
686 - placeholder="' . esc_attr( $field['placeholder'] ) . '"
867 +
868 + $output .= '<input
869 + type="' . esc_attr( $field['type'] ) . '"
870 + name="' . esc_attr( $key ) . '"
871 + id="' . esc_attr( $field['id'] ) . '"
872 + value="' . esc_attr( $field['value'] ) . '"
873 + placeholder="' . esc_attr( $field['placeholder'] ) . '"
687 874 class="' . esc_attr( $field['class'] ) . '"
688 875 style="' . esc_attr( $field['style'] ) . '"
689 876 ' . ( ($field['required']) ? 'required' : '' ) . '
690 877 >';
691 -
878 +
692 879 $output .= $field['after'];
693 -
694 - break;
880 +
881 + break;
695 882 }
696 - case "textarea":
883 + case "textarea":
697 884 {
698 885 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
699 - $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 ) . '">';
700 887 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
701 888 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
702 889 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
703 890 $field['placeholder'] = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
704 891 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
705 -
892 +
706 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.
707 895 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
708 896 {
709 - $field['value'] = $_GET[$key];
897 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
898 + $field['value'] = sanitize_textarea_field( wp_unslash( $_GET[$key] ) );
710 899 }
711 -
900 + else
901 + {
902 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
903 + {
904 + $value = get_post_meta( $post->ID, '_' . $key, true );
905 + if ( $value != '' )
906 + {
907 + $field['value'] = $value;
908 + }
909 + }
910 + }
911 +
712 912 $output .= $field['before'];
713 -
913 +
714 914 if ($field['show_label'])
715 915 {
716 916 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'];
717 917 if ($field['required'])
@@ -719,75 +919,160 @@
719 919 $output .= '<span class="required"> *</span>';
720 920 }
721 921 $output .= '</label>';
722 922 }
723 -
724 - $output .= '<textarea
725 - name="' . esc_attr( $key ) . '"
726 - id="' . esc_attr( $key ) . '"
923 +
924 + $output .= '<textarea
925 + name="' . esc_attr( $key ) . '"
926 + id="' . esc_attr( $key ) . '"
727 927 placeholder="' . esc_attr( $field['placeholder'] ) . '"
728 928 class="' . esc_attr( $field['class'] ) . '"
729 929 ' . ( ($field['required']) ? 'required' : '' ) . '
730 - >' . esc_attr( $field['value'] ) . '</textarea>';
731 -
930 + >' . esc_textarea( $field['value'] ) . '</textarea>';
931 +
732 932 $output .= $field['after'];
733 -
734 - break;
933 +
934 + break;
735 935 }
736 - case "radio":
936 + case "checkbox":
737 937 {
738 938 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
739 - $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 ) . '">';
740 940 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
941 + $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
942 + $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
943 + $field['label_style'] = isset( $field['label_style'] ) ? $field['label_style'] : '';
944 + $field['value'] = isset( $field['value'] ) ? $field['value'] : 'yes';
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.
947 + if ( isset( $_GET[$key] ) && sanitize_text_field(wp_unslash($_GET[$key])) == $field['value'] )
948 + {
949 + $field['checked'] = true;
950 + }
951 + else
952 + {
953 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
954 + {
955 + $value = get_post_meta( $post->ID, '_' . $key, true );
956 + if ( $value == 'yes' )
957 + {
958 + $field['checked'] = true;
959 + }
960 + }
961 + }
962 +
963 + $output .= $field['before'];
964 +
965 + $output .= '<label style="' . esc_attr( $field['label_style'] ) . '"><input
966 + type="' . esc_attr( $field['type'] ) . '"
967 + name="' . esc_attr( $key ) . '"
968 + value="' . esc_attr( $field['value'] ) . '"
969 + class="' . esc_attr( $field['class'] ) . '"
970 + ' . checked( $field['checked'], true, false ) . '
971 + >';
972 + if ($field['show_label'])
973 + {
974 + $output .= ' <span>' . $field['label'] . '</span>';
975 + }
976 + $output .= '</label>';
977 +
978 + $output .= $field['after'];
979 +
980 + break;
981 + }
982 + case "radio":
983 + {
984 + $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
985 + $field['before'] = isset( $field['before'] ) ? $field['before'] : '<div class="control control-' . esc_attr( $key ) . '">';
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'] : '';
741 991 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : false;
742 992 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
743 -
993 + $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
994 +
744 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.
745 997 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
746 998 {
747 - $field['value'] = $_GET[$key];
999 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1000 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
748 1001 }
749 -
1002 +
750 1003 $output .= $field['before'];
751 -
1004 +
752 1005 if ($field['show_label'])
753 1006 {
754 - $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>';
755 1014 }
756 -
757 - foreach ( $field['options'] as $option_key => $value )
1015 +
1016 + foreach ( $field['options'] as $option_key => $value )
758 1017 {
759 - $output .= '<label><input
760 - type="' . esc_attr( $field['type'] ) . '"
761 - name="' . esc_attr( $key ) . '"
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
1022 + type="' . esc_attr( $field['type'] ) . '"
1023 + name="' . esc_attr( $key ) . '"
1024 + id="' . $id . '"
762 1025 value="' . esc_attr( $option_key ) . '"
763 - class="' . esc_attr( $field['class'] ) . '"
1026 + class="' . esc_attr( $field['class'] ) . '"
764 1027 ' . checked( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
765 - > ' . 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']);
766 1032 }
767 -
1033 +
768 1034 $output .= $field['after'];
769 -
1035 +
770 1036 break;
771 1037 }
772 - case "select":
1038 + case "select":
773 1039 {
774 1040 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
775 - $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 ) . '">';
776 1042 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
777 1043 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
778 1044 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
779 1045 $field['required'] = isset( $field['required'] ) ? $field['required'] : false;
780 1046 $field['options'] = ( isset( $field['options'] ) && is_array( $field['options'] ) ) ? $field['options'] : array();
781 -
1047 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1048 +
1049 + if ( $field['multiselect'] )
1050 + {
1051 + wp_enqueue_script( 'multiselect' );
1052 + }
1053 +
782 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.
783 1056 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
784 1057 {
785 - $field['value'] = $_GET[$key];
1058 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1059 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
786 1060 }
787 -
1061 + else
1062 + {
1063 + if ( !is_post_type_archive('property') && !is_singular('property') && isset($post->ID) )
1064 + {
1065 + $value = get_post_meta( $post->ID, '_' . $key, true );
1066 + if ( $value != '' )
1067 + {
1068 + $field['value'] = $value;
1069 + }
1070 + }
1071 + }
1072 +
788 1073 $output .= $field['before'];
789 -
1074 +
790 1075 if ($field['show_label'])
791 1076 {
792 1077 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'];
793 1078 if ($field['required'])
@@ -795,64 +1080,112 @@
795 1080 $output .= '<span class="required"> *</span>';
796 1081 }
797 1082 $output .= '</label>';
798 1083 }
799 -
800 - $output .= '<select
801 - name="' . esc_attr( $key ) . '"
802 - id="' . esc_attr( $key ) . '"
803 - class="' . esc_attr( $field['class'] ) . '"
1084 +
1085 + $blank_option = '';
1086 + foreach ( $field['options'] as $option_key => $value )
1087 + {
1088 + if ( $field['multiselect'] && $option_key == '' )
1089 + {
1090 + $blank_option = $value;
1091 + continue;
1092 + }
1093 + }
1094 +
1095 + $output .= '<select
1096 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1097 + id="' . esc_attr( $key ) . '"
1098 + class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1099 + ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) . '
1100 + data-blank-option="' . esc_attr($blank_option) . '"
804 1101 >';
805 -
806 - foreach ( $field['options'] as $option_key => $value )
1102 +
1103 + foreach ( $field['options'] as $option_key => $value )
807 1104 {
808 - $output .= '<option
809 - value="' . esc_attr( $option_key ) . '"
810 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
811 - >' . esc_html( $value ) . '</option>';
1105 + if ( $field['multiselect'] && $option_key == '' )
1106 + {
1107 + // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1108 + continue;
1109 + }
1110 +
1111 + $output .= '<option
1112 + value="' . esc_attr( $option_key ) . '"';
1113 + if ( !$field['multiselect'] )
1114 + {
1115 + $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1116 + }
1117 + else
1118 + {
1119 + if (
1120 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1121 + ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1122 + ||
1123 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1124 + ( !isset($_REQUEST[$key]) && is_array($field['value']) && in_array($option_key, $field['value']) )
1125 + )
1126 + {
1127 + $output .= ' selected';
1128 + }
1129 + }
1130 + $output .= '>' . esc_html( $value ) . '</option>';
812 1131 }
813 -
1132 +
814 1133 $output .= '</select>';
815 -
1134 +
816 1135 $output .= $field['after'];
817 -
1136 +
818 1137 break;
819 1138 }
820 - case "office":
1139 + case "office":
821 1140 {
822 1141 $key = 'officeID';
823 -
1142 +
824 1143 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
825 - $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 ) . '">';
826 1145 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
827 1146 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
828 1147 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
829 -
1148 + $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
1149 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1150 +
1151 + if ( $field['multiselect'] )
1152 + {
1153 + wp_enqueue_script( 'multiselect' );
1154 + }
1155 +
830 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.
831 1158 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
832 1159 {
833 - $field['value'] = $_GET[$key];
1160 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1161 + $field['value'] = (int)$_GET[$key];
834 1162 }
835 -
1163 +
836 1164 $output .= $field['before'];
837 -
1165 +
838 1166 if ($field['show_label'])
839 1167 {
840 1168 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
841 1169 }
842 -
843 - $output .= '<select
844 - name="' . esc_attr( $key ) . '"
845 - id="' . esc_attr( $key ) . '"
846 - class="' . esc_attr( $field['class'] ) . '"
847 - >';
848 1170
849 - $output .= '<option
850 - value=""
1171 + $output .= '<select
1172 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1173 + id="' . esc_attr( $key ) . '"
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 + >';
1178 +
1179 + if ( !$field['multiselect'] )
1180 + {
1181 + $output .= '<option
1182 + value=""
851 1183 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
852 - >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
1184 + >' . esc_html( $field['blank_option'] ) . '</option>';
1185 + }
853 1186
854 - $args = array(
1187 + $args = array(
855 1188 'post_type' => 'office',
856 1189 'nopaging' => true,
857 1190 'orderby' => 'title',
858 1191 'order' => 'ASC'
@@ -857,61 +1190,72 @@
857 1190 'orderby' => 'title',
858 1191 'order' => 'ASC'
859 1192 );
860 1193 $office_query = new WP_Query($args);
861 -
1194 +
862 1195 if ($office_query->have_posts())
863 1196 {
864 1197 while ($office_query->have_posts())
865 1198 {
866 1199 $office_query->the_post();
867 -
868 - $output .= '<option
869 - value="' . esc_attr( $post->ID ) . '"
870 - ' . selected( esc_attr( $field['value'] ), esc_attr( $post->ID ), false ) . '
871 - >' . esc_html( get_the_title() ) . '</option>';
872 -
1200 +
1201 + $output .= '<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>';
1216 +
873 1217 }
874 1218 }
875 1219 wp_reset_postdata();
876 -
1220 +
877 1221 $output .= '</select>';
878 -
1222 +
879 1223 $output .= $field['after'];
880 -
1224 +
881 1225 break;
882 1226 }
883 - case "country":
1227 + case "country":
884 1228 {
885 - $key = 'country';
886 -
887 1229 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
888 - $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 ) . '">';
889 1231 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
890 1232 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
891 1233 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
892 -
1234 +
893 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.
894 1237 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
895 1238 {
896 - $field['value'] = $_GET[$key];
1239 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1240 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
897 1241 }
898 -
1242 +
899 1243 $output .= $field['before'];
900 -
1244 +
901 1245 if ($field['show_label'])
902 1246 {
903 1247 $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
904 1248 }
905 -
906 - $output .= '<select
907 - name="' . esc_attr( $key ) . '"
908 - id="' . esc_attr( $key ) . '"
1249 +
1250 + $output .= '<select
1251 + name="' . esc_attr( $key ) . '"
1252 + id="' . esc_attr( $key ) . '"
909 1253 class="' . esc_attr( $field['class'] ) . '"
910 1254 >';
911 1255
912 - $output .= '<option
913 - value=""
1256 + $output .= '<option
1257 + value=""
914 1258 ' . selected( esc_attr( $field['value'] ), esc_attr( '' ), false ) . '
915 1259 >' . esc_html( __( 'No preference', 'propertyhive' ) ) . '</option>';
916 1260
917 1261 $countries = get_option( 'propertyhive_countries', array() );
@@ -924,113 +1268,448 @@
924 1268 $ph_country = $ph_countries->get_country( $country );
925 1269
926 1270 if ( $ph_country !== FALSE )
927 1271 {
928 - $output .= '<option
929 - value="' . esc_attr( $country ) . '"
1272 + $output .= '<option
1273 + value="' . esc_attr( $country ) . '"
930 1274 ' . selected( esc_attr( $field['value'] ), esc_attr( $country ), false ) . '
931 1275 >' . esc_html( $ph_country['name'] ) . '</option>';
932 1276 }
933 1277 }
934 1278 }
935 -
1279 +
936 1280 $output .= '</select>';
937 -
1281 +
938 1282 $output .= $field['after'];
939 -
1283 +
940 1284 break;
941 1285 }
942 - case "hidden":
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 + }
1412 + case "hidden":
943 1413 {
944 1414 $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
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.
945 1417 if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
946 1418 {
947 - $field['value'] = $_GET[$key];
1419 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1420 + $field['value'] = sanitize_text_field(wp_unslash($_GET[$key]));
948 1421 }
949 -
950 - $output .= '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . $field['value'] . '">';
1422 +
1423 + $output .= '<input type="hidden" name="' . esc_attr( $field['name'] ) . '" value="' . esc_attr($field['value']) . '">';
951 1424 break;
952 1425 }
953 - case "html":
1426 + case "html":
954 1427 {
955 1428 $field['html'] = isset( $field['html'] ) ? $field['html'] : '';
956 - $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 ) . '">';
957 1430 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
958 1431
959 1432 $output .= $field['before'];
960 1433 $output .= $field['html'];
961 1434 $output .= $field['after'];
962 -
963 - break;
1435 +
1436 + break;
964 1437 }
1438 + case "recaptcha":
1439 + {
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' );
1476 +
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 +
1512 + break;
1513 + }
965 1514 default:
966 1515 {
967 1516 if ( taxonomy_exists($field['type']) )
968 1517 {
969 1518 $field['class'] = isset( $field['class'] ) ? $field['class'] : '';
970 - $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 ) . '">';
971 1520 $field['after'] = isset( $field['after'] ) ? $field['after'] : '</div>';
972 1521 $field['show_label'] = isset( $field['show_label'] ) ? $field['show_label'] : true;
973 1522 $field['label'] = isset( $field['label'] ) ? $field['label'] : '';
974 1523 $field['blank_option'] = isset( $field['blank_option'] ) ? $field['blank_option'] : __( 'No preference', 'propertyhive' );
975 -
976 - $field['value'] = isset( $field['value'] ) ? $field['value'] : '';
977 - if ( isset( $_GET[$key] ) && ! empty( $_GET[$key] ) )
1524 + $field['parent_terms_only'] = isset( $field['parent_terms_only'] ) ? $field['parent_terms_only'] : false;
1525 + $field['hide_empty'] = isset( $field['hide_empty'] ) ? $field['hide_empty'] : false;
1526 + $field['multiselect'] = isset( $field['multiselect'] ) ? $field['multiselect'] : false;
1527 + $field['dynamic_population'] = ( isset( $field['dynamic_population'] ) && $field['type'] == 'location' && $field['parent_terms_only'] === false && $field['multiselect'] === false ) ? $field['dynamic_population'] : false; // only applies to location
1528 +
1529 + if ( $field['multiselect'] )
978 1530 {
979 - $field['value'] = $_GET[$key];
1531 + wp_enqueue_script( 'multiselect' );
980 1532 }
981 1533
982 - $output .= $field['before'];
983 -
984 - if ($field['show_label'])
985 - {
986 - $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
987 - }
988 -
989 - $output .= '<select
990 - name="' . esc_attr( $key ) . '"
991 - id="' . esc_attr( $key ) . '"
992 - class="' . esc_attr( $field['class'] ) . '"
993 - >';
994 -
995 - $options = array( '' => $field['blank_option'] );
1534 + $options = array(
1535 + '' => array(
1536 + 'label' => $field['blank_option'],
1537 + 'parent' => 0
1538 + )
1539 + );
996 1540 $args = array(
997 - 'hide_empty' => false,
1541 + 'hide_empty' => $field['hide_empty'],
998 1542 'parent' => 0
999 1543 );
1000 - $terms = get_terms( $field['type'], $args );
1001 -
1002 - $selected_value = '';
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'] ) ) );
1546 +
1547 + $levels_of_taxonomy = 1;
1003 1548 if ( !empty( $terms ) && !is_wp_error( $terms ) )
1004 1549 {
1005 1550 foreach ($terms as $term)
1006 1551 {
1007 - $options[$term->term_id] = $term->name;
1008 -
1009 - $args = array(
1010 - 'hide_empty' => false,
1011 - 'parent' => $term->term_id
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 + );
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
1012 1589 );
1013 - $subterms = get_terms( $field['type'], $args );
1014 -
1015 - if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1590 +
1591 + if ($field['dynamic_population'])
1592 + $levels_of_taxonomy = max(1, $levels_of_taxonomy);
1593 +
1594 + if (
1595 + !isset($field['parent_terms_only'])
1596 + ||
1597 + (
1598 + isset($field['parent_terms_only']) &&
1599 + $field['parent_terms_only'] === false
1600 + )
1601 + )
1016 1602 {
1017 - foreach ($subterms as $term)
1603 + $args = array(
1604 + 'hide_empty' => $field['hide_empty'],
1605 + 'parent' => $term->term_id,
1606 + );
1607 + $args = apply_filters( 'propertyhive_form_taxonomy_terms_args', $args, $field );
1608 + $args = apply_filters( 'propertyhive_form_taxonomy_subterms_args', $args, $field );
1609 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $field['type'] ) ) );
1610 +
1611 + if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
1018 1612 {
1019 - $options[$term->term_id] = '- ' . $term->name;
1020 -
1021 - $args = array(
1022 - 'hide_empty' => false,
1023 - 'parent' => $term->term_id
1024 - );
1025 - $subsubterms = get_terms( $field['type'], $args );
1026 -
1027 - if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1613 + foreach ($subterms as $subterm)
1028 1614 {
1029 - foreach ($subsubterms as $term)
1615 + if ( isset($field['hide_empty']) && $field['hide_empty'] === true )
1030 1616 {
1031 - $options[$term->term_id] = '- ' . $term->name;
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 + );
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 + }
1032 1647 }
1648 +
1649 + $options[(int)$subterm->term_id] = array(
1650 + 'label' => ( !$field['dynamic_population'] ? '- ' : '' ) . $subterm->name,
1651 + 'parent' => (int)$term->term_id,
1652 + );
1653 +
1654 + if ($field['dynamic_population'])
1655 + $levels_of_taxonomy = max(2, $levels_of_taxonomy);
1656 +
1657 + $args = array(
1658 + 'hide_empty' => $field['hide_empty'],
1659 + 'parent' => (int)$subterm->term_id
1660 + );
1661 + $args = apply_filters( 'propertyhive_form_taxonomy_terms_args', $args, $field );
1662 + $args = apply_filters( 'propertyhive_form_taxonomy_subsubterms_args', $args, $field );
1663 + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => $field['type'] ) ) );
1664 +
1665 + if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
1666 + {
1667 + foreach ($subsubterms as $subsubterm)
1668 + {
1669 + if ( isset($field['hide_empty']) && $field['hide_empty'] === true )
1670 + {
1671 + $empty_check_args = array(
1672 + 'post_type' => 'property',
1673 + 'posts_per_page' => 1,
1674 + 'fields' => 'ids',
1675 + 'no_found_rows' => true,
1676 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1677 + 'meta_query' => array(
1678 + array(
1679 + 'key' => '_on_market',
1680 + 'value' => 'yes',
1681 + ),
1682 + ),
1683 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Existence-only published-property check for one taxonomy term and on-market meta; fetches one ID without row counts, with existing extension query filter retained.
1684 + 'tax_query' => array(
1685 + array(
1686 + 'taxonomy' => $field['type'],
1687 + 'field' => 'term_id',
1688 + 'terms' => $subsubterm->term_id,
1689 + ),
1690 + ),
1691 + );
1692 +
1693 + $empty_check_args = apply_filters( 'propertyhive_taxonomy_hide_empty_args', $empty_check_args, $field, $subsubterm->term_id );
1694 +
1695 + $empty_check_query = new WP_Query( $empty_check_args );
1696 +
1697 + if ( !$empty_check_query->have_posts() )
1698 + {
1699 + continue;
1700 + }
1701 + }
1702 +
1703 + $options[(int)$subsubterm->term_id] = array(
1704 + 'label' => ( !$field['dynamic_population'] ? '- - ' : '' ) . $subsubterm->name,
1705 + 'parent' => (int)$subterm->term_id,
1706 + );
1707 +
1708 + if ($field['dynamic_population'])
1709 + $levels_of_taxonomy = max(3, $levels_of_taxonomy);
1710 + }
1711 + }
1033 1712 }
1034 1713 }
1035 1714 }
1036 1715 }
@@ -1035,21 +1714,112 @@
1035 1714 }
1036 1715 }
1037 1716 }
1038 1717
1039 - foreach ( $options as $option_key => $value )
1718 + if ( $field['dynamic_population'] )
1040 1719 {
1041 - $output .= '<option
1042 - value="' . esc_attr( $option_key ) . '"
1043 - ' . selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false ) . '
1044 - >' . esc_html( $value ) . '</option>';
1720 + wp_localize_script( 'propertyhive_dynamic_population', 'propertyhive_dynamic_population_params', array(
1721 + 'options' => $options,
1722 + 'levels_of_taxonomy' => $levels_of_taxonomy,
1723 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1724 + 'value' => isset($_GET[$field['type']]) ? ph_clean( wp_unslash( $_GET[$field['type']] ) ) : '',
1725 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1726 + 'other_values' => ( isset($_GET['other_' . $field['type']]) && is_array($_GET['other_' . $field['type']]) && !empty($_GET['other_' . $field['type']]) ) ? array_filter( array_filter( ph_clean( wp_unslash( $_GET['other_' . $field['type']] ) ) ), 'is_scalar' ) : array(),
1727 + 'taxonomy' => $field['type'],
1728 + ) );
1729 + wp_enqueue_script( 'propertyhive_dynamic_population' );
1045 1730 }
1046 1731
1047 - $output .= '</select>';
1048 -
1049 - $output .= $field['after'];
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'])
1745 + {
1746 + $output .= '<label for="' . esc_attr( $key ) . '">' . $field['label'] . '</label>';
1747 + }
1748 +
1749 + $output .= '<select
1750 + name="' . esc_attr( $key ) . ( $field['multiselect'] ? '[]' : '' ) . '"
1751 + id="' . esc_attr( $key ) . '"
1752 + class="' . esc_attr( $field['class'] ) . ( $field['multiselect'] ? ' ph-form-multiselect' : '' ) . '"
1753 + ' . ( $field['multiselect'] ? ' multiple="multiple"' : '' ) .
1754 + ( $field['dynamic_population'] ? ' data-dynamic-population-level="' . $level_i . '"' : '' ) .
1755 + ( ( $field['dynamic_population'] && $level_i > 1 ) ? ' disabled' : '' ) . '
1756 + data-blank-option="' . esc_attr($field['blank_option']) . '"
1757 + >';
1758 +
1759 + if ( $level_i == 1 )
1760 + {
1761 + foreach ( $options as $option_key => $value )
1762 + {
1763 + if ( $field['multiselect'] && $option_key == '' )
1764 + {
1765 + // Skip because we don't want a blank option in the multiselect. Instead use $value as the placeholder
1766 + continue;
1767 + }
1768 +
1769 + if ( $field['dynamic_population'] && $value['parent'] != '0' )
1770 + {
1771 + continue;
1772 + }
1773 +
1774 + $output .= '<option
1775 + value="' . esc_attr( $option_key ) . '"';
1776 + if ( !$field['multiselect'] )
1777 + {
1778 + $output .= selected( esc_attr( $field['value'] ), esc_attr( $option_key ), false );
1779 + }
1780 + else
1781 + {
1782 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only public form preferences; these values do not authorize or perform a state change.
1783 + if ( isset($_REQUEST[$key]) && is_array($_REQUEST[$key]) && in_array($option_key, $_REQUEST[$key]) )
1784 + {
1785 + $output .= ' selected';
1786 + }
1787 + elseif ( is_array($field['value']) && in_array($option_key, $field['value']) )
1788 + {
1789 + $output .= ' selected';
1790 + }
1791 + }
1792 + $output .= '>' . esc_html( $value['label'] ) . '</option>';
1793 + }
1794 + }
1795 +
1796 + $output .= '</select>';
1797 +
1798 + $output .= $field['after'];
1799 +
1800 + if ( $field['type'] == 'availability' )
1801 + {
1802 + $availability_departments = get_option( 'propertyhive_availability_departments', array() );
1803 + if ( !is_array($availability_departments) ) { $availability_departments = array(); }
1804 +
1805 + if ( !empty($availability_departments) )
1806 + {
1807 +?>
1808 +<script>
1809 +<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial availability selection, reduced to an integer before JavaScript output. ?>
1810 +var selected_availability = '<?php echo ( isset($_REQUEST[$key]) && is_scalar( $_REQUEST[$key] ) && $_REQUEST[$key] != '' ? (int)$_REQUEST[$key] : '' ); ?>';
1811 +var availability_departments = <?php echo wp_json_encode( $availability_departments , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1812 +var availabilities = <?php echo wp_json_encode( $options , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1813 +var availabilities_order = <?php echo wp_json_encode( array_keys($options) , JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>;
1814 +</script>
1815 +<?php
1816 + }
1817 + }
1818 + }
1050 1819 }
1051 1820 }
1052 1821 }
1053 -
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).
1054 1824 echo $output;
1055 -}
1825 +}