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/admin/settings/class-ph-settings-general.php +668 -100 1.4.612.3.1 View file →
@@ -1,5 +1,8 @@
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 +
2 5 /**
3 6 * PropertyHive General Settings
4 7 *
5 8 * @author PropertyHive
@@ -16,8 +19,9 @@
16 19
17 20 /**
18 21 * PH_Settings_General
19 22 */
23 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Settings_General; preserving the existing PH_* class name is required for plugin and extension compatibility.
20 24 class PH_Settings_General extends PH_Settings_Page {
21 25
22 26 /**
23 27 * Constructor.
@@ -40,11 +44,14 @@
40 44 public function get_sections() {
41 45 $sections = array(
42 46 '' => __( 'General', 'propertyhive' ),
43 47 'modules' => __( 'Modules', 'propertyhive' ),
48 + 'map' => __( 'Map', 'propertyhive' ),
49 + 'media' => __( 'Media', 'propertyhive' ),
44 50 'international' => __( 'International', 'propertyhive' ),
45 - 'map' => __( 'Map', 'propertyhive' ),
46 51 'gdpr' => __( 'GDPR', 'propertyhive' ),
52 + 'captcha' => __( 'CAPTCHA', 'propertyhive' ),
53 + 'text-substitution' => __( 'Text Substitution', 'propertyhive' ),
47 54 'misc' => __( 'Miscellaneous', 'propertyhive' ),
48 55 );
49 56
50 57 return $sections;
@@ -56,10 +63,12 @@
56 63 *
57 64 * @return array
58 65 */
59 66 public function get_settings() {
60 -
67 +
68 + $default_departments = ph_get_departments(true);
61 69 $departments = ph_get_departments();
70 + $custom_departments = ph_get_custom_departments(false);
62 71
63 72 $settings = array(
64 73
65 74 array( 'title' => __( 'General Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'general_options' ),
@@ -65,28 +74,87 @@
65 74 array( 'title' => __( 'General Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'general_options' ),
66 75
67 76 );
68 77
69 - $i = 0;
78 + $active_departments_html = '<div id="active_departments">';
79 +
70 80 foreach ( $departments as $key => $value )
71 81 {
72 - $checkboxgroup = 'middle';
73 - if ( $i == 0 ) { $checkboxgroup = 'start'; }
74 - if ( $i == (count($departments) - 1) ) { $checkboxgroup = 'end'; }
82 + if ( isset($custom_departments[$key]) )
83 + {
84 +
85 + }
86 + else
87 + {
88 + $active_departments_html .= '<fieldset>
89 + <legend class="screen-reader-text"><span>' . esc_html(__( 'Active Departments', 'propertyhive' )) . '</span></legend>
90 + <label for="propertyhive_active_departments_' . esc_attr(str_replace("residential-", "", $key)) . '">
91 + <input name="propertyhive_active_departments_' . esc_attr(str_replace("residential-", "", $key)) . '" id="propertyhive_active_departments_' . esc_attr(str_replace("residential-", "", $key)) . '" type="checkbox" value="1" ' . checked( get_option('propertyhive_active_departments_' . str_replace("residential-", "", $key)) , 'yes', false ) . '> ' . esc_html($value) . '
92 + </label>
93 + </fieldset>';
94 + }
95 + }
75 96
76 - $settings[] = array(
77 - 'title' => __( 'Active Departments', 'propertyhive' ),
78 - 'desc' => $value,
79 - 'id' => 'propertyhive_active_departments_' . str_replace("residential-", "", $key),
80 - 'type' => 'checkbox',
81 - 'default' => ( ( in_array($key, array('residential-sales', 'residential-lettings', 'commercial')) ) ? 'yes' : '' ),
82 - 'checkboxgroup' => $checkboxgroup,
83 - );
97 + if ( !empty($custom_departments) )
98 + {
99 + foreach ( $custom_departments as $key => $custom_department )
100 + {
101 + // key will be custom-0, for example
102 + $active_departments_html .= '<fieldset id="propertyhive_active_department_fieldset_' . esc_attr($key) . '">
103 + <legend class="screen-reader-text"><span>' . esc_html(__( 'Active Departments', 'propertyhive' )) . '</span></legend>
104 + <label>
105 + <input name="propertyhive_active_departments_' . esc_attr($key) . '" type="checkbox" value="1" ' . checked( get_option('propertyhive_active_departments_' . $key), 'yes', false ) . '>
106 + <input type="text" name="propertyhive_active_departments_name_' . esc_attr($key) . '" value="' . $custom_department['name'] . '">
107 + ' . __( 'based on', 'propertyhive' ) . '
108 + <select name="propertyhive_active_departments_based_on_' . esc_attr($key) . '">
109 + <option value=""></option>';
110 + foreach ( $default_departments as $dept_key => $value )
111 + {
112 + $active_departments_html .= '<option value="' . esc_attr($dept_key) . '"' . selected( $custom_department['based_on'], $dept_key, false ) . '>' . esc_html($value) . '</option>';
113 + }
114 + $active_departments_html .= '
115 + </select>
116 + <a href="" class="delete-department" data-department="' . esc_attr($key) . '">Delete</a>
117 + </label>
118 + </fieldset>';
119 + }
120 + }
84 121
85 - ++$i;
86 - }
122 + $active_departments_html .= '</div>
123 + <a href="" id="add_department">+ ' . esc_html(__( 'Add Department', 'propertyhive' )) . '</a>
124 + <input type="hidden" name="propertyhive_new_custom_departments" id="propertyhive_new_custom_departments" value="0">
125 + <input type="hidden" name="propertyhive_custom_departments" id="propertyhive_custom_departments" value="' . esc_attr(implode(",", array_keys($custom_departments))) . '">
126 + <input type="hidden" name="propertyhive_custom_departments_original" id="propertyhive_custom_departments_original" value="' . esc_attr(implode(",", array_keys($custom_departments))) . '">
127 + ';
87 128
129 + $active_departments_html .= '<div id="active_department_template" style="display:none">
130 + <fieldset id="propertyhive_active_department_fieldset_template">
131 + <legend class="screen-reader-text"><span>' . __( 'Active Departments', 'propertyhive' ) . '</span></legend>
132 + <label>
133 + <input name="propertyhive_active_departments_template" type="checkbox" value="1" checked>
134 + <input type="text" name="propertyhive_active_departments_name_template" value="">
135 + ' . __( 'based on', 'propertyhive' ) . '
136 + <select name="propertyhive_active_departments_based_on_template">
137 + <option value=""></option>';
138 + foreach ( $default_departments as $key => $value )
139 + {
140 + $active_departments_html .= '<option value="' . esc_attr($key) . '">' . esc_html($value) . '</option>';
141 + }
142 + $active_departments_html .= '
143 + </select>
144 + <a href="" class="delete-department" data-department="template">Delete</a>
145 + </label>
146 + </fieldset>
147 + </div>';
148 +
88 149 $settings[] = array(
150 + 'title' => __( 'Active Departments', 'propertyhive' ),
151 + 'id' => 'propertyhive_active_departments',
152 + 'type' => 'html',
153 + 'html' => $active_departments_html,
154 + );
155 +
156 + $settings[] = array(
89 157 'title' => __( 'Primary Department', 'propertyhive' ),
90 158 'id' => 'propertyhive_primary_department',
91 159 'type' => 'radio',
92 160 'default' => 'residential-sales',
@@ -190,8 +258,17 @@
190 258 ),
191 259
192 260 array(
193 261 'title' => __( 'Disabled Modules', 'propertyhive' ),
262 + 'desc' => __( 'Tenancies', 'propertyhive' ),
263 + 'id' => 'propertyhive_module_disabled_tenancies',
264 + 'type' => 'checkbox',
265 + 'default' => '',
266 + 'checkboxgroup' => 'middle'
267 + ),
268 +
269 + array(
270 + 'title' => __( 'Disabled Modules', 'propertyhive' ),
194 271 'desc' => __( 'Enquiries', 'propertyhive' ),
195 272 'id' => 'propertyhive_module_disabled_enquiries',
196 273 'type' => 'checkbox',
197 274 'default' => '',
@@ -213,15 +290,8 @@
213 290 $settings = array(
214 291
215 292 array( 'title' => __( 'International Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'international_options' ),
216 293
217 - array(
218 - 'title' => __( 'Default Country', 'propertyhive' ),
219 - 'id' => 'propertyhive_default_country',
220 - 'type' => 'single_select_country',
221 - 'css' => 'min-width:300px;',
222 - ),
223 -
224 294 array(
225 295 'title' => __( 'Countries Where You Operate', 'propertyhive' ),
226 296 'id' => 'propertyhive_countries',
227 297 'type' => 'multi_select_countries',
@@ -229,13 +299,21 @@
229 299 'desc' => __( 'Hold ctrl/cmd whilst clicking to select multiple', 'propertyhive' )
230 300 ),
231 301
232 302 array(
303 + 'title' => __( 'Default Country', 'propertyhive' ),
304 + 'id' => 'propertyhive_default_country',
305 + 'type' => 'single_select_country',
306 + 'css' => 'min-width:300px;',
307 + ),
308 +
309 + array(
233 310 'title' => __( 'Price Thousand Separator', 'propertyhive' ),
234 311 'id' => 'propertyhive_price_thousand_separator',
235 312 'type' => 'text',
236 313 'default' => ',',
237 314 'css' => 'width:50px;',
315 + 'desc' => __( 'This only effects prices output on the frontend. Prices entered and displayed in the backend will use the comma character (,) as the thousand separator.', 'propertyhive' )
238 316 ),
239 317
240 318 array(
241 319 'title' => __( 'Price Decimal Separator', 'propertyhive' ),
@@ -242,8 +320,9 @@
242 320 'id' => 'propertyhive_price_decimal_separator',
243 321 'type' => 'text',
244 322 'default' => '.',
245 323 'css' => 'width:50px;',
324 + 'desc' => __( 'This only effects prices output on the frontend. Prices entered and displayed in the backend will use the period character (.) as the decimal separator.', 'propertyhive' )
246 325 )
247 326 );
248 327
249 328 $ph_countries = new PH_Countries();
@@ -276,9 +355,9 @@
276 355 $settings[] = array(
277 356 'type' => 'html',
278 357 'html' => '<script>
279 358
280 - var countries = '. json_encode( $countries ) . ';
359 + var countries = '. wp_json_encode( $countries, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';
281 360
282 361 </script>'
283 362 );
284 363
@@ -296,14 +375,48 @@
296 375
297 376 array( 'title' => __( 'Map Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'map_options' ),
298 377
299 378 array(
379 + 'title' => __( 'Maps Provider', 'propertyhive' ),
380 + 'id' => 'propertyhive_maps_provider',
381 + 'type' => 'radio',
382 + 'options' => array(
383 + '' => 'Google Maps',
384 + 'mapbox' => 'Mapbox',
385 + 'osm' => 'OpenStreetMap',
386 + ),
387 + ),
388 +
389 + array(
300 390 'title' => __( 'Google Maps API Key', 'propertyhive' ),
301 391 'id' => 'propertyhive_google_maps_api_key',
302 392 'type' => 'text',
303 393 'desc' => '<p>' . __( 'If you have a Google Maps API key you can enter it here. You can generate an API key <a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank">here</a>.<br>This is used when displaying the map when adding/editing properties, and if using our <a href="https://wp-property-hive.com/addons/map-search/" target="_blank">Map Search</a> or <a href="https://wp-property-hive.com/addons/radial-search/" target="_blank">Radial Search</a> add ons.<br>When creating your API key we recommend that you enable the Geocoding library. More about this can be found <a href="https://docs.wp-property-hive.com/user-guide/maps-co-ordinates-and-geocoding/" target="_blank">here</a>.', 'propertyhive' ) . '</p>'
304 - )
394 + ),
305 395
396 + array(
397 + 'title' => __( 'Mapbox API Key', 'propertyhive' ),
398 + 'id' => 'propertyhive_mapbox_api_key',
399 + 'type' => 'text',
400 + 'desc' => '<p>' . __( 'If you have a Mapbox API key you can enter it here. You can generate an API key <a href="https://account.mapbox.com/" target="_blank">here</a>.', 'propertyhive' ) . '</p>'
401 + ),
402 +
403 + array(
404 + 'title' => __( 'Geocoding Provider', 'propertyhive' ),
405 + 'id' => 'propertyhive_geocoding_provider',
406 + 'type' => 'radio',
407 + 'options' => array(
408 + '' => 'Google Maps',
409 + //'mapbox' => 'Mapbox',
410 + 'osm' => 'OpenStreetMap',
411 + ),
412 + ),
413 +
414 + array(
415 + 'id' => 'propertyhive_osm_html',
416 + 'type' => 'html',
417 + 'html' => '<p>' . __( 'Note: OSM is an open source project maintained by volunteers and the geocoding results returned are reliant on the data sets available. If you find some addresses/search terms don\'t return results this is likely the issue, as opposed to it being an issue with Property Hive.', 'propertyhive' ) . '</p>'
418 + ),
306 419 );
307 420
308 421 if ( apply_filters( 'propertyhive_use_google_maps_geocoding_api_key', false) === true )
309 422 {
@@ -312,8 +425,15 @@
312 425 'id' => 'propertyhive_google_maps_geocoding_api_key',
313 426 'type' => 'text',
314 427 'desc' => '<p>' . __( 'If you have referer restrictions applied to the main API key entered then server side geocoding requests will be blocked. To get around this you can setup a separate API key specifically for geocoding and enter it here, with IP restrictions applied instead if required.<br>More about this can be found <a href="https://docs.wp-property-hive.com/user-guide/maps-co-ordinates-and-geocoding/" target="_blank">here</a>.', 'propertyhive' ) . '</p>'
315 428 );
429 +
430 + $settings[] = array(
431 + 'title' => __( 'Mapbox Geocoding API Key', 'propertyhive' ),
432 + 'id' => 'propertyhive_mapbox_geocoding_api_key',
433 + 'type' => 'text',
434 + 'desc' => '<p>' . __( 'If you have referer restrictions applied to the main API key entered then server side geocoding requests will be blocked. To get around this you can setup a separate API key specifically for geocoding and enter it here, with IP restrictions applied instead if required.<br>More about this can be found <a href="https://docs.wp-property-hive.com/user-guide/maps-co-ordinates-and-geocoding/" target="_blank">here</a>.', 'propertyhive' ) . '</p>'
435 + );
316 436 }
317 437
318 438 $settings[] = array( 'type' => 'sectionend', 'id' => 'map_options');
319 439
@@ -320,8 +440,74 @@
320 440 return apply_filters( 'propertyhive_general_map_settings', $settings );
321 441 }
322 442
323 443 /**
444 + * Get media settings array
445 + *
446 + * @return array
447 + */
448 + public function get_general_media_setting() {
449 +
450 + $settings = array(
451 +
452 + array( 'title' => __( 'Property Media Storage', 'propertyhive' ), 'type' => 'title', 'desc' => 'By default media attached to properties will be stored in the WordPress media library. If the property media is hosted elsewhere (for example if you import properties from a third party and they allow you to link direct to the files on their server) you can choose to store media as URL\'s. It also means that the images are not downloaded onto your server, thus saving diskspace.<br><br>Note: If you change this you\'ll need to re-add all of the property media for existing properties.<br>Note: Changing this will not delete all existing media or URLs entered.<br>Note: Choosing to store the media as URLs will mean you don\'t benefit from having access to different sized images (i.e. small, medium, large etc).', 'id' => 'media_storage_options' ),
453 +
454 + array(
455 + 'title' => __( 'Images Stored As', 'propertyhive' ),
456 + 'id' => 'propertyhive_images_stored_as',
457 + 'type' => 'select',
458 + 'default' => 'files',
459 + 'options' => array(
460 + 'files' => __( 'Media Files', 'propertyhive' ),
461 + 'urls' => __( 'URLs', 'propertyhive' ),
462 + ),
463 + 'desc' => ''
464 + ),
465 +
466 + array(
467 + 'title' => __( 'Floorplans Stored As', 'propertyhive' ),
468 + 'id' => 'propertyhive_floorplans_stored_as',
469 + 'type' => 'select',
470 + 'default' => 'files',
471 + 'options' => array(
472 + 'files' => __( 'Media Files', 'propertyhive' ),
473 + 'urls' => __( 'URLs', 'propertyhive' ),
474 + ),
475 + 'desc' => ''
476 + ),
477 +
478 + array(
479 + 'title' => __( 'Brochures Stored As', 'propertyhive' ),
480 + 'id' => 'propertyhive_brochures_stored_as',
481 + 'type' => 'select',
482 + 'default' => 'files',
483 + 'options' => array(
484 + 'files' => __( 'Media Files', 'propertyhive' ),
485 + 'urls' => __( 'URLs', 'propertyhive' ),
486 + ),
487 + 'desc' => ''
488 + ),
489 +
490 + array(
491 + 'title' => __( 'EPCs Stored As', 'propertyhive' ),
492 + 'id' => 'propertyhive_epcs_stored_as',
493 + 'type' => 'select',
494 + 'default' => 'files',
495 + 'options' => array(
496 + 'files' => __( 'Media Files', 'propertyhive' ),
497 + 'urls' => __( 'URLs', 'propertyhive' ),
498 + ),
499 + 'desc' => ''
500 + ),
501 +
502 + array( 'type' => 'sectionend', 'id' => 'media_storage_options')
503 +
504 + );
505 +
506 + return apply_filters( 'propertyhive_general_media_settings', $settings );
507 + }
508 +
509 + /**
324 510 * Get GDPR settings array
325 511 *
326 512 * @return array
327 513 */
@@ -357,8 +543,167 @@
357 543
358 544 ) ); // End general GDPR settings
359 545 }
360 546
547 + /**
548 + * Get CAPTCHA settings array
549 + *
550 + * @return array
551 + */
552 + public function get_general_captcha_setting() {
553 +
554 + return apply_filters( 'propertyhive_general_captcha_settings', array(
555 +
556 + array( 'title' => __( 'CAPTCHA Settings', 'propertyhive' ), 'type' => 'title', 'id' => 'captcha_options', 'desc' => __( 'Prevent spam and bots on forms provided by Property Hive by signing up to a CAPTCHA service and entering the details below.<br><br>When enabled, a CAPTCHA will be added to the register, property enquiry and send to friend forms.', 'propertyhive' ) ),
557 +
558 + array(
559 + 'title' => __( 'CAPTCHA service', 'propertyhive' ),
560 + 'id' => 'propertyhive_captcha_service',
561 + 'type' => 'radio',
562 + 'options' => array(
563 + '' => __( 'None', 'propertyhive' ),
564 + 'recaptcha' => __( 'Google reCaptcha v2', 'propertyhive' ) . ' (<a href="https://www.google.com/recaptcha/admin/create" target="_blank">register</a>)',
565 + 'recaptcha-v3' => __( 'Google reCaptcha v3', 'propertyhive' ) . ' (<a href="https://www.google.com/recaptcha/admin/create" target="_blank">register</a>)',
566 + 'hCaptcha' => __( 'hCaptcha', 'propertyhive' ) . ' (<a href="https://www.hcaptcha.com/" target="_blank">register</a>)',
567 + 'turnstile' => __( 'Cloudflare Turnstile', 'propertyhive' ) . ' (<a href="https://www.cloudflare.com/en-gb/application-services/products/turnstile/" target="_blank">register</a>)', // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent -- Registration hyperlink for the optional CAPTCHA provider.
568 + ),
569 + ),
570 +
571 + array(
572 + 'title' => __( 'Site Key', 'propertyhive' ),
573 + 'id' => 'propertyhive_captcha_site_key',
574 + 'type' => 'text',
575 + ),
576 +
577 + array(
578 + 'title' => __( 'Secret', 'propertyhive' ),
579 + 'id' => 'propertyhive_captcha_secret',
580 + 'type' => 'text',
581 + ),
582 +
583 + array(
584 + 'title' => __( 'Score Threshold', 'propertyhive' ),
585 + 'id' => 'propertyhive_captcha_score_threshold',
586 + 'type' => 'text',
587 + 'default' => 0.5,
588 + 'desc' => __( 'Set the minimum score required to pass reCAPTCHA v3 verification (between 0 and 1). Lower values allow more submissions through but increase spam risk.', 'propertyhive' ),
589 + ),
590 +
591 + array(
592 + //'title' => __( 'CAPTCHA service', 'propertyhive' ),
593 + 'id' => 'propertyhive_captcha_html',
594 + 'type' => 'html',
595 + 'html' => '<script>
596 + jQuery(document).ready(function()
597 + {
598 + jQuery(\'input[name=\\\'propertyhive_captcha_service\\\']\').change(function()
599 + {
600 + ph_captcha_service_update();
601 + });
602 +
603 + ph_captcha_service_update();
604 + });
605 +
606 + function ph_captcha_service_update()
607 + {
608 + jQuery(\'#row_propertyhive_captcha_site_key\').hide();
609 + jQuery(\'#row_propertyhive_captcha_secret\').hide();
610 + jQuery(\'#row_propertyhive_captcha_score_threshold\').hide();
611 +
612 + var selected_captcha_service = jQuery(\'input[name=\\\'propertyhive_captcha_service\\\']:checked\').val();
613 +
614 + if ( selected_captcha_service != \'\' )
615 + {
616 + jQuery(\'#row_propertyhive_captcha_site_key\').show();
617 + jQuery(\'#row_propertyhive_captcha_secret\').show();
618 +
619 + if ( selected_captcha_service == \'recaptcha-v3\' )
620 + {
621 + jQuery(\'#row_propertyhive_captcha_score_threshold\').show();
622 + }
623 + }
624 + }
625 + </script>',
626 + ),
627 +
628 + array( 'type' => 'sectionend', 'id' => 'captcha_options'),
629 +
630 + ) ); // End general CAPTCHA settings
631 + }
632 +
633 + /**
634 + * Get text substitution settings array
635 + *
636 + * @return array
637 + */
638 + public function get_general_text_substitution_setting() {
639 +
640 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
641 +
642 + $settings = array(
643 +
644 + array( 'title' => __( 'Text Substitution', 'propertyhive' ), 'type' => 'title', 'desc' => 'Replace specific words or phrases used throughout Property Hive with your own preferred wording.', 'id' => 'template_assistant_text_translation_settings' )
645 +
646 + );
647 +
648 + $existing_translations = array();
649 + if ( isset($current_settings['text_translations']) && is_array($current_settings['text_translations']) && !empty($current_settings['text_translations']) )
650 + {
651 + foreach ( $current_settings['text_translations'] as $text_translation )
652 + {
653 + $existing_translations[] = '<tr>
654 + <td><input type="text" name="search[]" class="regular-text" value="' . esc_attr($text_translation['search']) . '"></td>
655 + <td><input type="text" name="replace[]" class="regular-text" value="' . esc_attr($text_translation['replace']) . '"></td>
656 + <td><a href="" class="delete-text-substitution" title="Delete Text Substitution"><span class="dashicons dashicons-trash"></span></a></td>
657 + </tr>';
658 + }
659 + }
660 +
661 + $settings[] = array(
662 + 'type' => 'html',
663 + 'full_width' => true,
664 + 'html' => '
665 +
666 + <table class="widefat striped" style="max-width:520px;" >
667 + <thead>
668 + <tr>
669 + <th style="padding:8px 10px;" scope="col">Text To Replace</th>
670 + <th style="padding:8px 10px;" scope="col">Replace With</th>
671 + <th style="padding:8px 10px;" scope="col">&nbsp;</th>
672 + </tr>
673 + </thead>
674 + <tbody>
675 + ' . implode("", $existing_translations) . '
676 + <tr>
677 + <td><input type="text" name="search[]" class="regular-text" placeholder="e.g. Make Enquiry"></td>
678 + <td><input type="text" name="replace[]" class="regular-text" placeholder="e.g. Request Viewing"></td>
679 + <td>&nbsp;</td>
680 + </tr>
681 + </tbody>
682 + </table>
683 +
684 + <script>
685 + jQuery(document).ready(function()
686 + {
687 + jQuery(\'a.delete-text-substitution\').on(\'click\', function(e)
688 + {
689 + e.preventDefault();
690 +
691 + jQuery(this).closest(\'tr\').find(\'input\').val(\'\');
692 +
693 + setTimeout(function() {
694 + jQuery(\'.submit button[type="submit"]\').trigger(\'click\');
695 + }, 0);
696 + });
697 + });
698 + </script>'
699 + );
700 +
701 + $settings[] = array( 'type' => 'sectionend', 'id' => 'template_assistant_text_translation_settings');
702 +
703 + return $settings;
704 + }
705 +
361 706 /**
362 707 * Get misc settings array
363 708 *
364 709 * @return array
@@ -376,10 +721,11 @@
376 721 'default' => '=',
377 722 'options' => array(
378 723 '=' => __( 'Match Keyword Exactly', 'propertyhive' ),
379 724 'LIKE' => __( 'Perform Loose Search', 'propertyhive' ),
725 + 'polygon' => __( 'Search Within Location Perimeter', 'propertyhive' ),
380 726 ),
381 - 'desc_tip' => __( 'Applicable if you allow users to search by entering a location. If \'Match Keyword Exactly\' is selected a search for \'Walton\' would not return properties in \'Walton On Thames\', but would prevent properties in \'Lincolnshire\' appearing when searching for \'Lincoln\'. \'Perform Loose Search\' would do the opposite.', 'propertyhive' )
727 + 'desc_tip' => __( 'Applicable if you allow users to search by entering a location. If \'Match Keyword Exactly\' is selected a search for \'Walton\' would not return properties in \'Walton On Thames\', but would prevent properties in \'Lincolnshire\' appearing when searching for \'Lincoln\'. \'Perform Loose Search\' would do the opposite. \'Search Within Location Perimeter\' will make an API request to Nominatim to get the perimeter of the entered location and will search within the returned polygon.', 'propertyhive' )
382 728 ),
383 729
384 730 array(
385 731 'title' => __( 'Commercial Display In Search Results', 'propertyhive' ),
@@ -394,8 +740,21 @@
394 740 ),
395 741 ),
396 742
397 743 array(
744 + 'title' => __( 'When An Off Market Property URL Is Viewed', 'propertyhive' ),
745 + 'id' => 'propertyhive_off_market_behaviour',
746 + 'type' => 'select',
747 + 'default' => '',
748 + 'css' => 'min-width:300px;',
749 + 'options' => array(
750 + '' => __( 'Still show the property details', 'propertyhive' ),
751 + 'redirect' => __( 'Do a 301 redirect to the search results page', 'propertyhive' ),
752 + ),
753 + 'desc' => __( 'If 301 redirect is selected, this will only apply to non-logged in users', 'propertyhive' )
754 + ),
755 +
756 + array(
398 757 'title' => __( 'When Entering Features', 'propertyhive' ),
399 758 'id' => 'propertyhive_features_type',
400 759 'type' => 'radio',
401 760 'options' => array(
@@ -441,12 +800,39 @@
441 800 'id' => 'propertyhive_my_account_page_id',
442 801 'type' => 'single_select_page',
443 802 'default' => '',
444 803 'css' => 'min-width:300px;',
445 - 'desc' => __( 'This sets the \'My Account\' page. This page should contain the [propertyhive_my_account] shortcode', 'propertyhive' ),
804 + 'desc' => __( 'This page should contain the [propertyhive_my_account] shortcode', 'propertyhive' ),
446 805 ),
447 806
448 807 array(
808 + 'title' => __( 'Applicant Login Page', 'propertyhive' ),
809 + 'id' => 'propertyhive_applicant_login_page_id',
810 + 'type' => 'single_select_page',
811 + 'default' => '',
812 + 'css' => 'min-width:300px;',
813 + 'desc' => __( 'This page should contain the [propertyhive_login_form] shortcode', 'propertyhive' ),
814 + ),
815 +
816 + array(
817 + 'title' => __( 'Applicant Reset Password Page', 'propertyhive' ),
818 + 'id' => 'propertyhive_applicant_reset_password_page_id',
819 + 'type' => 'single_select_page',
820 + 'default' => '',
821 + 'css' => 'min-width:300px;',
822 + 'desc' => __( 'This page should contain the [propertyhive_reset_password_form] shortcode', 'propertyhive' ),
823 + ),
824 +
825 + array(
826 + 'title' => __( 'Applicant Registration Page', 'propertyhive' ),
827 + 'id' => 'propertyhive_applicant_registration_page_id',
828 + 'type' => 'single_select_page',
829 + 'default' => '',
830 + 'css' => 'min-width:300px;',
831 + 'desc' => __( 'This page should contain the [applicant_registration_form] shortcode', 'propertyhive' ),
832 + ),
833 +
834 + array(
449 835 'title' => __( 'Match Price Range % (Lower)', 'propertyhive' ),
450 836 'id' => 'propertyhive_applicant_match_price_range_percentage_lower',
451 837 'type' => 'number',
452 838 'default' => '20',
@@ -459,61 +845,32 @@
459 845 'type' => 'number',
460 846 'default' => '5',
461 847 ),
462 848
463 - array( 'type' => 'sectionend', 'id' => 'applicant_registration_options'),
464 -
465 - array( 'title' => __( 'Property Media Storage', 'propertyhive' ), 'type' => 'title', 'desc' => 'By default media attached to properties will be stored in the WordPress media library. If the property media is hosted elsewhere (for example if you import properties from a third party and they allow you to link direct to the files on their server) you can choose to store media as URL\'s. It also means that the images are not downloaded onto your server, thus saving diskspace.<br><br>Note: If you change this you\'ll need to re-add all of the property media for existing properties.<br>Note: Changing this will not delete all existing media or URLs entered.<br>Note: Choosing to store the media as URLs will mean you don\'t benefit from having access to different sized images (i.e. small , medium, large etc).', 'id' => 'media_storage_options' ),
466 -
467 849 array(
468 - 'title' => __( 'Images Stored As', 'propertyhive' ),
469 - 'id' => 'propertyhive_images_stored_as',
470 - 'type' => 'select',
471 - 'default' => 'files',
850 + 'title' => __( 'When Selecting Applicant Locations', 'propertyhive' ),
851 + 'id' => 'propertyhive_applicant_locations_type',
852 + 'type' => 'radio',
472 853 'options' => array(
473 - 'files' => __( 'Media Files', 'propertyhive' ),
474 - 'urls' => __( 'URLs', 'propertyhive' ),
854 + 'text' => __( 'Allow Locations To Be Freetyped', 'propertyhive' ),
855 + '' => __( 'Select From A Predefined List (Editable from \'Custom Fields\')', 'propertyhive' ),
475 856 ),
476 - 'desc' => __( '', 'propertyhive' )
857 + 'desc' => '<i>' . __( 'When this setting is changed, locations selected using the other method will no longer be used for matching', 'propertyhive' ) . '</i>',
477 858 ),
478 859
479 - array(
480 - 'title' => __( 'Floorplans Stored As', 'propertyhive' ),
481 - 'id' => 'propertyhive_floorplans_stored_as',
482 - 'type' => 'select',
483 - 'default' => 'files',
484 - 'options' => array(
485 - 'files' => __( 'Media Files', 'propertyhive' ),
486 - 'urls' => __( 'URLs', 'propertyhive' ),
487 - ),
488 - 'desc' => __( '', 'propertyhive' )
489 - ),
860 + array( 'type' => 'sectionend', 'id' => 'applicant_registration_options'),
490 861
491 - array(
492 - 'title' => __( 'Brochures Stored As', 'propertyhive' ),
493 - 'id' => 'propertyhive_brochures_stored_as',
494 - 'type' => 'select',
495 - 'default' => 'files',
496 - 'options' => array(
497 - 'files' => __( 'Media Files', 'propertyhive' ),
498 - 'urls' => __( 'URLs', 'propertyhive' ),
499 - ),
500 - 'desc' => __( '', 'propertyhive' )
501 - ),
862 + array( 'title' => __( 'Improve Property Hive', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'stat_tracking_options' ),
502 863
503 864 array(
504 - 'title' => __( 'EPCs Stored As', 'propertyhive' ),
505 - 'id' => 'propertyhive_epcs_stored_as',
506 - 'type' => 'select',
507 - 'default' => 'files',
508 - 'options' => array(
509 - 'files' => __( 'Media Files', 'propertyhive' ),
510 - 'urls' => __( 'URLs', 'propertyhive' ),
511 - ),
512 - 'desc' => __( '', 'propertyhive' )
865 + 'title' => __( 'Usage Data Sharing', 'propertyhive' ),
866 + 'id' => 'propertyhive_data_sharing',
867 + 'type' => 'checkbox',
868 + 'value' => ( get_option('propertyhive_data_sharing', FALSE) === FALSE ? 'yes' : get_option('propertyhive_data_sharing', '') ),
869 + 'desc' => __( 'Help improve Property Hive by periodically sending us non-sensitive plugin data. Untick this option to opt-out. <a href="https://docs.wp-property-hive.com/user-guide/share-usage-data/" target="_blank">Learn more</a>', 'propertyhive' )
513 870 ),
514 871
515 - array( 'type' => 'sectionend', 'id' => 'media_storage_options'),
872 + array( 'type' => 'sectionend', 'id' => 'stat_tracking_options'),
516 873
517 874 ) ); // End general misc settings
518 875 }
519 876
@@ -529,9 +886,12 @@
529 886 {
530 887 case "modules": { $settings = $this->get_general_modules_setting(); break; }
531 888 case "international": { $settings = $this->get_general_international_setting(); break; }
532 889 case "map": { $settings = $this->get_general_map_setting(); break; }
890 + case "media": { $settings = $this->get_general_media_setting(); break; }
533 891 case "gdpr": { $settings = $this->get_general_gdpr_setting(); break; }
892 + case "captcha": { $settings = $this->get_general_captcha_setting(); break; }
893 + case "text-substitution": { $settings = $this->get_general_text_substitution_setting(); break; }
534 894 case "misc": { $settings = $this->get_general_misc_setting(); break; }
535 895 default: { die("Unknown setting section"); }
536 896 }
537 897 }
@@ -553,9 +913,9 @@
553 913 * @param string $desc (default: '')
554 914 * @return void
555 915 */
556 916 function color_picker( $name, $id, $value, $desc = '' ) {
557 - echo '<div class="color_box"><strong><img class="help_tip" data-tip="' . esc_attr( $desc ) . '" src="' . PH()->plugin_url() . '/assets/images/help.png" height="16" width="16" /> ' . esc_html( $name ) . '</strong>
917 + echo '<div class="color_box"><strong><img class="help_tip" data-tip="' . esc_attr( $desc ) . '" src="' . esc_url(PH()->plugin_url() . '/assets/images/help.png') . '" height="16" width="16" /> ' . esc_html( $name ) . '</strong>
558 918 <input name="' . esc_attr( $id ). '" id="' . esc_attr( $id ) . '" type="text" value="' . esc_attr( $value ) . '" class="colorpick" /> <div id="colorPickerDiv_' . esc_attr( $id ) . '" class="colorpickdiv"></div>
559 919 </div>';
560 920 }
561 921
@@ -562,8 +922,12 @@
562 922 /**
563 923 * Save settings
564 924 */
565 925 public function save() {
926 + if ( ! current_user_can( 'manage_options' ) || ! isset( $_REQUEST['_wpnonce'] ) || ! is_string( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'propertyhive-settings' ) ) {
927 + return;
928 + }
929 +
566 930 global $current_section;
567 931
568 932 if ( $current_section != '' )
569 933 {
@@ -575,36 +939,48 @@
575 939
576 940 PH_Admin_Settings::save_fields( $settings );
577 941 break;
578 942 }
579 - case 'international':
580 - {
581 - if (!isset($_POST['propertyhive_countries']) || (isset($_POST['propertyhive_countries']) && empty($_POST['propertyhive_countries'])))
582 - {
583 - // If we haven't selected which countries we operate in
584 - update_option( 'propertyhive_countries', array( ph_clean($_POST['propertyhive_default_country']) ) );
585 - }
586 - else
587 - {
588 - // We have default country and countries set
589 - // Make sure default country is in list of countries selected
590 - if ( !in_array(ph_clean($_POST['propertyhive_default_country']), ph_clean($_POST['propertyhive_countries'])) ) {
591 - $_POST['propertyhive_default_country'] = $_POST['propertyhive_countries'][0];
592 - }
593 -
594 - update_option( 'propertyhive_default_country', ph_clean($_POST['propertyhive_default_country']) );
595 - update_option( 'propertyhive_countries', ph_clean($_POST['propertyhive_countries']) );
596 - }
597 -
598 - update_option( 'propertyhive_price_thousand_separator', ph_clean($_POST['propertyhive_price_thousand_separator']) );
599 - update_option( 'propertyhive_price_decimal_separator', ph_clean($_POST['propertyhive_price_decimal_separator']) );
600 -
601 - update_option( 'propertyhive_search_form_currency', ph_clean($_POST['propertyhive_search_form_currency']) );
602 -
603 - do_action( 'propertyhive_update_currency_exchange_rates' );
604 -
605 - break;
606 - }
943 + case 'international':
944 + {
945 + $international = array();
946 + foreach ( array( 'propertyhive_default_country', 'propertyhive_price_thousand_separator', 'propertyhive_price_decimal_separator', 'propertyhive_search_form_currency' ) as $input_key ) {
947 + if ( ! isset( $_POST[$input_key] ) || ! is_string( $_POST[$input_key] ) ) {
948 + return;
949 + }
950 + $international[$input_key] = sanitize_text_field( wp_unslash( $_POST[$input_key] ) );
951 + }
952 + if ( isset( $_POST['propertyhive_countries'] ) && ! is_array( $_POST['propertyhive_countries'] ) ) {
953 + return;
954 + }
955 + $countries = array();
956 + if ( isset( $_POST['propertyhive_countries'] ) ) {
957 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each country is validated as a string, unslashed and sanitized in the loop before use.
958 + foreach ( $_POST['propertyhive_countries'] as $country ) {
959 + if ( ! is_string( $country ) ) {
960 + return;
961 + }
962 + $countries[] = sanitize_text_field( wp_unslash( $country ) );
963 + }
964 + }
965 + if ( empty( $countries ) ) {
966 + // Preserve the existing default-country fallback when no list is selected.
967 + update_option( 'propertyhive_countries', array( $international['propertyhive_default_country'] ) );
968 + } else {
969 + if ( ! in_array( $international['propertyhive_default_country'], $countries, true ) ) {
970 + $international['propertyhive_default_country'] = $countries[0];
971 + // Keep the normalized selection available to existing save-hook consumers.
972 + $_POST['propertyhive_default_country'] = wp_slash( $countries[0] );
973 + }
974 + update_option( 'propertyhive_default_country', $international['propertyhive_default_country'] );
975 + update_option( 'propertyhive_countries', $countries );
976 + }
977 + update_option( 'propertyhive_price_thousand_separator', $international['propertyhive_price_thousand_separator'] );
978 + update_option( 'propertyhive_price_decimal_separator', $international['propertyhive_price_decimal_separator'] );
979 + update_option( 'propertyhive_search_form_currency', $international['propertyhive_search_form_currency'] );
980 + do_action( 'propertyhive_update_currency_exchange_rates' );
981 + break;
982 + }
607 983 case 'map':
608 984 {
609 985 $settings = $this->get_general_map_setting();
610 986
@@ -610,8 +986,15 @@
610 986
611 987 PH_Admin_Settings::save_fields( $settings );
612 988 break;
613 989 }
990 + case 'media':
991 + {
992 + $settings = $this->get_general_media_setting();
993 +
994 + PH_Admin_Settings::save_fields( $settings );
995 + break;
996 + }
614 997 case 'gdpr':
615 998 {
616 999 $settings = $this->get_general_gdpr_setting();
617 1000
@@ -617,8 +1000,57 @@
617 1000
618 1001 PH_Admin_Settings::save_fields( $settings );
619 1002 break;
620 1003 }
1004 + case 'captcha':
1005 + {
1006 + $settings = $this->get_general_captcha_setting();
1007 +
1008 + PH_Admin_Settings::save_fields( $settings );
1009 + break;
1010 + }
1011 + case 'text-substitution':
1012 + {
1013 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1014 +
1015 + if ( ! is_array( $current_settings ) ) {
1016 + $current_settings = array();
1017 + }
1018 + foreach ( array( 'search', 'replace' ) as $field ) {
1019 + if ( isset( $_POST[ $field ] ) && ! is_array( $_POST[ $field ] ) ) {
1020 + return;
1021 + }
1022 + }
1023 + $text_translations = array();
1024 + if ( isset($_POST['search']) && is_array($_POST['search']) && !empty($_POST['search']) && isset($_POST['replace']) && is_array($_POST['replace']) && !empty($_POST['replace']) )
1025 + {
1026 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Search members and paired replacement strings are shape-checked, unslashed and sanitized below.
1027 + foreach ( $_POST['search'] as $i => $search )
1028 + {
1029 + if ( ! is_string( $search ) || ! isset( $_POST['replace'][$i] ) || ! is_string( $_POST['replace'][$i] ) ) {
1030 + return;
1031 + }
1032 + $search = sanitize_text_field( wp_unslash( $search ) );
1033 + $replace = sanitize_text_field( wp_unslash( $_POST['replace'][$i] ) );
1034 + if ( trim( $search ) !== '' && trim( $replace ) !== '' )
1035 + {
1036 + $text_translations[] = array(
1037 + 'search' => $search,
1038 + 'replace' => $replace,
1039 + );
1040 + }
1041 + }
1042 + }
1043 +
1044 + $propertyhive_template_assistant = array(
1045 + 'text_translations' => $text_translations,
1046 + );
1047 +
1048 + $propertyhive_template_assistant = array_merge($current_settings, $propertyhive_template_assistant);
1049 +
1050 + update_option( 'propertyhive_template_assistant', $propertyhive_template_assistant );
1051 + break;
1052 + }
621 1053 case 'misc':
622 1054 {
623 1055 $settings = $this->get_general_misc_setting();
624 1056
@@ -629,13 +1061,149 @@
629 1061 }
630 1062 }
631 1063 else
632 1064 {
1065 + // Validate the complete custom-department form before any options are changed.
1066 + $department_lists = array();
1067 + foreach ( array( 'propertyhive_custom_departments', 'propertyhive_custom_departments_original' ) as $field ) {
1068 + if ( isset( $_POST[ $field ] ) && ! is_string( $_POST[ $field ] ) ) {
1069 + return;
1070 + }
1071 + $raw_list = isset( $_POST[ $field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $field ] ) ) : '';
1072 + $department_lists[ $field ] = array_filter( explode( ',', $raw_list ) );
1073 + }
1074 + $department_details = array();
1075 + foreach ( $department_lists['propertyhive_custom_departments'] as $department_id ) {
1076 + foreach ( array( 'name', 'based_on' ) as $detail ) {
1077 + $field = 'propertyhive_active_departments_' . $detail . '_' . $department_id;
1078 + if ( ! isset( $_POST[ $field ] ) || ! is_string( $_POST[ $field ] ) ) {
1079 + return;
1080 + }
1081 + $department_details[ $department_id ][ $detail ] = sanitize_text_field( wp_unslash( $_POST[ $field ] ) );
1082 + }
1083 + }
1084 +
633 1085 $settings = $this->get_settings();
634 1086
635 1087 PH_Admin_Settings::save_fields( $settings );
636 1088
637 - flush_rewrite_rules();
1089 + $departments = ph_get_departments();
1090 +
1091 + foreach ( $departments as $key => $value )
1092 + {
1093 + if ( substr($key, 0, 6) == 'phnew-' )
1094 + {
1095 + // Ignore new departments as we'll deal with them separately
1096 + continue;
1097 + }
1098 +
1099 + if (
1100 + isset($_POST['propertyhive_active_departments_' . str_replace("residential-", "", $key)])
1101 + )
1102 + {
1103 + $option_value = 'yes';
1104 + }
1105 + else
1106 + {
1107 + $option_value = 'no';
1108 + }
1109 +
1110 + update_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key), $option_value );
1111 + }
1112 +
1113 + $custom_departments = array();
1114 + if ( ! empty( $department_lists['propertyhive_custom_departments'] ) )
1115 + {
1116 + $submitted_custom_departments = $department_lists['propertyhive_custom_departments'];
1117 + $submitted_custom_departments = array_filter($submitted_custom_departments);
1118 + if ( !empty($submitted_custom_departments) )
1119 + {
1120 + foreach ( $submitted_custom_departments as $submitted_custom_department )
1121 + {
1122 + if (
1123 + isset($_POST['propertyhive_active_departments_' . $submitted_custom_department])
1124 + )
1125 + {
1126 + $option_value = 'yes';
1127 + }
1128 + else
1129 + {
1130 + $option_value = 'no';
1131 + }
1132 +
1133 + $key = $submitted_custom_department;
1134 + if ( substr($submitted_custom_department, 0, 6) == 'phnew-' )
1135 + {
1136 + $key = sanitize_title( $department_details[ $submitted_custom_department ]['name'] );
1137 + }
1138 +
1139 + $custom_departments[$key] = array(
1140 + 'name' => $department_details[ $submitted_custom_department ]['name'],
1141 + 'based_on' => $department_details[ $submitted_custom_department ]['based_on']
1142 + );
1143 +
1144 + update_option( 'propertyhive_active_departments_' . $key, $option_value );
1145 + }
1146 + }
1147 + }
1148 +
1149 + update_option( 'propertyhive_custom_departments', $custom_departments );
1150 +
1151 + // TO DO: Cater for deleted departments
1152 + if ( ! empty( $department_lists['propertyhive_custom_departments_original'] ) )
1153 + {
1154 + $original_custom_departments = $department_lists['propertyhive_custom_departments_original'];
1155 + $original_custom_departments = array_filter($original_custom_departments);
1156 + if ( !empty($original_custom_departments) )
1157 + {
1158 + $submitted_custom_departments = $department_lists['propertyhive_custom_departments'];
1159 + $submitted_custom_departments = array_filter($submitted_custom_departments);
1160 +
1161 + foreach ( $original_custom_departments as $original_custom_department )
1162 + {
1163 + if ( !in_array($original_custom_department, $submitted_custom_departments) )
1164 + {
1165 + // no longer present
1166 + delete_option( 'propertyhive_active_departments_' . $original_custom_department );
1167 + }
1168 + }
1169 + }
1170 + }
1171 +
1172 + // Update departments in any search forms
1173 + $current_settings = get_option( 'propertyhive_template_assistant', array() );
1174 +
1175 + if ( isset($current_settings['search_forms']) && !empty($current_settings['search_forms']) )
1176 + {
1177 + foreach ( $current_settings['search_forms'] as $id => $form )
1178 + {
1179 + if ( isset($form['active_fields']) && isset($form['active_fields']['department']) && isset($form['active_fields']['department']['options']) )
1180 + {
1181 + // We have a department field in this form. Check options match current department settings
1182 +
1183 + $departments = ph_get_departments();
1184 +
1185 + foreach ( $departments as $key => $value )
1186 + {
1187 + $department_active = get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) );
1188 +
1189 + if ( $department_active != 'yes' && isset($form['active_fields']['department']['options'][$key]) )
1190 + {
1191 + unset($form['active_fields']['department']['options'][$key]);
1192 + }
1193 +
1194 + if ( $department_active == 'yes' && !isset($form['active_fields']['department']['options'][$key]) )
1195 + {
1196 + $form['active_fields']['department']['options'][$key] = $value;
1197 + }
1198 + }
1199 +
1200 + $current_settings['search_forms'][$id]['active_fields'] = $form['active_fields'];
1201 + }
1202 + }
1203 + }
1204 +
1205 + update_option( 'propertyhive_template_assistant', $current_settings );
638 1206 }
639 1207 }
640 1208
641 1209 }