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 +903 -103 1.4.52.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.
@@ -38,13 +42,17 @@
38 42 * @return array
39 43 */
40 44 public function get_sections() {
41 45 $sections = array(
42 - '' => __( 'General', 'propertyhive' ),
43 - 'modules' => __( 'Modules', 'propertyhive' ),
44 - 'international' => __( 'International', 'propertyhive' ),
45 - 'map' => __( 'Map', 'propertyhive' ),
46 - 'misc' => __( 'Miscellaneous', 'propertyhive' ),
46 + '' => __( 'General', 'propertyhive' ),
47 + 'modules' => __( 'Modules', 'propertyhive' ),
48 + 'map' => __( 'Map', 'propertyhive' ),
49 + 'media' => __( 'Media', 'propertyhive' ),
50 + 'international' => __( 'International', 'propertyhive' ),
51 + 'gdpr' => __( 'GDPR', 'propertyhive' ),
52 + 'captcha' => __( 'CAPTCHA', 'propertyhive' ),
53 + 'text-substitution' => __( 'Text Substitution', 'propertyhive' ),
54 + 'misc' => __( 'Miscellaneous', 'propertyhive' ),
47 55 );
48 56
49 57 return $sections;
50 58 }
@@ -55,71 +63,147 @@
55 63 *
56 64 * @return array
57 65 */
58 66 public function get_settings() {
59 -
60 - return apply_filters( 'propertyhive_general_settings', array(
67 +
68 + $default_departments = ph_get_departments(true);
69 + $departments = ph_get_departments();
70 + $custom_departments = ph_get_custom_departments(false);
61 71
72 + $settings = array(
73 +
62 74 array( 'title' => __( 'General Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'general_options' ),
75 +
76 + );
77 +
78 + $active_departments_html = '<div id="active_departments">';
79 +
80 + foreach ( $departments as $key => $value )
81 + {
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 + }
96 +
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 + }
121 +
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 + ';
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 +
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(
157 + 'title' => __( 'Primary Department', 'propertyhive' ),
158 + 'id' => 'propertyhive_primary_department',
159 + 'type' => 'radio',
160 + 'default' => 'residential-sales',
161 + 'options' => $departments,
162 + );
63 163
64 - array(
65 - 'title' => __( 'Active Departments', 'propertyhive' ),
66 - 'desc' => __( 'Residential Sales', 'propertyhive' ),
67 - 'id' => 'propertyhive_active_departments_sales',
68 - 'type' => 'checkbox',
69 - 'default' => 'yes',
70 - 'checkboxgroup' => 'start'
71 - ),
72 -
73 - array(
74 - 'title' => __( 'Active Departments', 'propertyhive' ),
75 - 'desc' => __( 'Residential Lettings', 'propertyhive' ),
76 - 'id' => 'propertyhive_active_departments_lettings',
77 - 'type' => 'checkbox',
78 - 'default' => 'yes',
79 - 'checkboxgroup' => 'middle'
80 - ),
164 + $settings[] = array(
165 + 'title' => __( 'Property Search Results Page', 'propertyhive' ),
166 + 'id' => 'propertyhive_search_results_page_id',
167 + 'type' => 'single_select_page',
168 + 'default' => '',
169 + 'css' => 'min-width:300px;',
170 + 'desc' => __( 'This sets the page of your property search results', 'propertyhive' ),
171 + );
81 172
82 - array(
83 - 'title' => __( 'Active Departments', 'propertyhive' ),
84 - 'desc' => __( 'Commercial', 'propertyhive' ),
85 - 'id' => 'propertyhive_active_departments_commercial',
86 - 'type' => 'checkbox',
87 - 'checkboxgroup' => 'end'
88 - ),
173 + $settings[] = array(
174 + 'title' => __( 'Lettings Fees (Residential)', 'propertyhive' ),
175 + 'id' => 'propertyhive_lettings_fees',
176 + 'type' => 'textarea',
177 + 'css' => 'height:150px; width:100%; max-width:400px'
178 + );
89 179
90 - array(
91 - 'title' => __( 'Primary Department', 'propertyhive' ),
92 - 'id' => 'propertyhive_primary_department',
93 - 'type' => 'radio',
94 - 'default' => 'residential-sales',
95 - 'options' => array(
96 - 'residential-sales' => __( 'Residential Sales', 'propertyhive' ),
97 - 'residential-lettings' => __( 'Residential Lettings', 'propertyhive' ),
98 - 'commercial' => __( 'Commercial', 'propertyhive' )
99 - )
100 - ),
101 -
102 - array(
103 - 'title' => __( 'Property Search Results Page', 'propertyhive' ),
104 - //'desc' => '<br/>' . sprintf( __( 'The base page can also be used in your <a href="%s">product permalinks</a>.', 'propertyhive' ), admin_url( 'options-permalink.php' ) ),
105 - 'id' => 'propertyhive_search_results_page_id',
106 - 'type' => 'single_select_page',
107 - 'default' => '',
108 - 'css' => 'min-width:300px;',
109 - 'desc' => __( 'This sets the page of your property search results', 'propertyhive' ),
110 - ),
180 + $settings[] = array(
181 + 'title' => __( 'Lettings Fees (Commercial)', 'propertyhive' ),
182 + 'id' => 'propertyhive_lettings_fees_commercial',
183 + 'type' => 'textarea',
184 + 'css' => 'height:150px; width:100%; max-width:400px'
185 + );
111 186
112 - array(
113 - 'title' => __( 'Lettings Fees', 'propertyhive' ),
114 - 'id' => 'propertyhive_lettings_fees',
115 - 'type' => 'textarea',
116 - 'css' => 'height:150px; width:100%; max-width:400px'
117 - ),
187 + $settings[] = array(
188 + 'title' => __( 'Display Link To Lettings Fees Next To Price', 'propertyhive' ),
189 + 'desc' => __( 'In Search Results', 'propertyhive' ),
190 + 'id' => 'propertyhive_lettings_fees_display_search_results',
191 + 'type' => 'checkbox',
192 + 'checkboxgroup' => 'start',
193 + );
194 +
195 + $settings[] = array(
196 + 'title' => __( 'Display Link To Lettings Fees Next To Price', 'propertyhive' ),
197 + 'desc' => __( 'On Property Details Page', 'propertyhive' ),
198 + 'id' => 'propertyhive_lettings_fees_display_single_property',
199 + 'type' => 'checkbox',
200 + 'checkboxgroup' => 'end',
201 + );
118 202
119 - array( 'type' => 'sectionend', 'id' => 'general_options'),
203 + $settings[] = array( 'type' => 'sectionend', 'id' => 'general_options');
120 204
121 - ) ); // End general settings
205 + return apply_filters( 'propertyhive_general_settings', $settings );
122 206 }
123 207
124 208 /**
125 209 * Get general modules settings array
@@ -144,8 +228,17 @@
144 228 'type' => 'checkbox',
145 229 'default' => '',
146 230 'checkboxgroup' => 'start'
147 231 ),
232 +
233 + array(
234 + 'title' => __( 'Disabled Modules', 'propertyhive' ),
235 + 'desc' => __( 'Appraisals', 'propertyhive' ),
236 + 'id' => 'propertyhive_module_disabled_appraisals',
237 + 'type' => 'checkbox',
238 + 'default' => '',
239 + 'checkboxgroup' => 'middle'
240 + ),
148 241
149 242 array(
150 243 'title' => __( 'Disabled Modules', 'propertyhive' ),
151 244 'desc' => __( 'Viewings', 'propertyhive' ),
@@ -165,8 +258,17 @@
165 258 ),
166 259
167 260 array(
168 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' ),
169 271 'desc' => __( 'Enquiries', 'propertyhive' ),
170 272 'id' => 'propertyhive_module_disabled_enquiries',
171 273 'type' => 'checkbox',
172 274 'default' => '',
@@ -184,13 +286,21 @@
184 286 * @return array
185 287 */
186 288 public function get_general_international_setting() {
187 289
188 - return apply_filters( 'propertyhive_general_international_settings', array(
290 + $settings = array(
189 291
190 292 array( 'title' => __( 'International Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'international_options' ),
191 293
192 - array(
294 + array(
295 + 'title' => __( 'Countries Where You Operate', 'propertyhive' ),
296 + 'id' => 'propertyhive_countries',
297 + 'type' => 'multi_select_countries',
298 + 'css' => 'min-width:300px;',
299 + 'desc' => __( 'Hold ctrl/cmd whilst clicking to select multiple', 'propertyhive' )
300 + ),
301 +
302 + array(
193 303 'title' => __( 'Default Country', 'propertyhive' ),
194 304 'id' => 'propertyhive_default_country',
195 305 'type' => 'single_select_country',
196 306 'css' => 'min-width:300px;',
@@ -196,18 +306,63 @@
196 306 'css' => 'min-width:300px;',
197 307 ),
198 308
199 309 array(
200 - 'title' => __( 'Countries Where You Operate', 'propertyhive' ),
201 - 'id' => 'propertyhive_countries',
202 - 'type' => 'multi_select_countries',
203 - 'css' => 'min-width:300px;',
204 - 'desc' => __( 'Hold ctrl/cmd whilst clicking to select multiple', 'propertyhive' )
310 + 'title' => __( 'Price Thousand Separator', 'propertyhive' ),
311 + 'id' => 'propertyhive_price_thousand_separator',
312 + 'type' => 'text',
313 + 'default' => ',',
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' )
205 316 ),
206 317
207 - array( 'type' => 'sectionend', 'id' => 'international_options'),
318 + array(
319 + 'title' => __( 'Price Decimal Separator', 'propertyhive' ),
320 + 'id' => 'propertyhive_price_decimal_separator',
321 + 'type' => 'text',
322 + 'default' => '.',
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' )
325 + )
326 + );
208 327
209 - ) ); // End general international settings
328 + $ph_countries = new PH_Countries();
329 + $ph_countries = $ph_countries->countries;
330 +
331 + $currencies = array();
332 + $countries = array();
333 + if ( !empty($ph_countries) )
334 + {
335 + foreach ( $ph_countries as $country_code => $country )
336 + {
337 + $currencies[$country['currency_code']] = $country['currency_code'];
338 + $countries[$country_code] = $country;
339 + }
340 + }
341 + $currencies = array_unique($currencies);
342 + ksort($currencies);
343 +
344 + $settings[] = array(
345 + 'title' => __( 'Currency Used In Search Forms', 'propertyhive' ),
346 + 'id' => 'propertyhive_search_form_currency',
347 + 'type' => 'select',
348 + 'options' => $currencies,
349 + 'default' => 'GBP',
350 + 'desc' => __( 'Please note that this doesn\'t change the currency symbol shown in price dropdowns within search forms. The easiest way to achieve that is to use our free <a href="https://wp-property-hive.com/addons/template-assistant/" target="_blank">Template Assistant add on</a>.', 'propertyhive' ),
351 + );
352 +
353 + $settings[] = array( 'type' => 'sectionend', 'id' => 'international_options');
354 +
355 + $settings[] = array(
356 + 'type' => 'html',
357 + 'html' => '<script>
358 +
359 + var countries = '. wp_json_encode( $countries, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';
360 +
361 + </script>'
362 + );
363 +
364 + return apply_filters( 'propertyhive_general_international_settings', $settings );
210 365 }
211 366
212 367 /**
213 368 * Get map settings array
@@ -215,24 +370,340 @@
215 370 * @return array
216 371 */
217 372 public function get_general_map_setting() {
218 373
219 - return apply_filters( 'propertyhive_general_map_settings', array(
374 + $settings = array(
220 375
221 376 array( 'title' => __( 'Map Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'map_options' ),
222 377
223 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(
224 390 'title' => __( 'Google Maps API Key', 'propertyhive' ),
225 391 'id' => 'propertyhive_google_maps_api_key',
226 392 'type' => 'text',
227 - 'desc' => __( 'If you have a Google Maps API key you can enter it here. A map is displayed when adding/editing properties, and if using our <a href="https://wp-property-hive.com/addons/map-search/" target="_blank">Map Search Add On</a>. You can generate an API key <a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank">here</a>.', 'propertyhive' )
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>'
228 394 ),
229 395
230 - array( 'type' => 'sectionend', 'id' => 'map_options'),
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 + ),
231 402
232 - ) ); // End general map settings
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 + ),
419 + );
420 +
421 + if ( apply_filters( 'propertyhive_use_google_maps_geocoding_api_key', false) === true )
422 + {
423 + $settings[] = array(
424 + 'title' => __( 'Google Maps Geocoding API Key', 'propertyhive' ),
425 + 'id' => 'propertyhive_google_maps_geocoding_api_key',
426 + 'type' => 'text',
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>'
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 + );
436 + }
437 +
438 + $settings[] = array( 'type' => 'sectionend', 'id' => 'map_options');
439 +
440 + return apply_filters( 'propertyhive_general_map_settings', $settings );
233 441 }
234 442
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 + /**
510 + * Get GDPR settings array
511 + *
512 + * @return array
513 + */
514 + public function get_general_gdpr_setting() {
515 +
516 + return apply_filters( 'propertyhive_general_gdpr_settings', array(
517 +
518 + array( 'title' => __( 'GDPR Settings', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'gdpr_options' ),
519 +
520 + array(
521 + 'title' => __( 'Store Property Enquiries', 'propertyhive' ),
522 + 'id' => 'propertyhive_store_property_enquiries',
523 + 'type' => 'checkbox',
524 + 'default' => 'yes',
525 + 'desc' => __( 'If using the default property enquiry form that comes with Property Hive, select this option if enquiries should be saved in the \'Enquiries\' section of Property Hive. Note that regardless of whether this is ticked or not, the enquiry will still be sent via email. This option simply determines whether we save it to the database or not.', 'propertyhive' )
526 + ),
527 +
528 + array(
529 + 'title' => __( 'Property Enquiry Form Disclaimer', 'propertyhive' ),
530 + 'id' => 'propertyhive_property_enquiry_form_disclaimer',
531 + 'type' => 'wysiwyg',
532 + 'desc' => __( 'Add disclaimer text, including a link to a privacy policy, that will appear on the property enquiry form.', 'propertyhive' )
533 + ),
534 +
535 + array(
536 + 'title' => __( 'Applicant Registration Form Disclaimer', 'propertyhive' ),
537 + 'id' => 'propertyhive_applicant_registration_form_disclaimer',
538 + 'type' => 'wysiwyg',
539 + 'desc' => __( 'Add disclaimer text, including a link to a privacy policy, that will appear on the applicant registration form.', 'propertyhive' )
540 + ),
541 +
542 + array( 'type' => 'sectionend', 'id' => 'gdpr_options'),
543 +
544 + ) ); // End general GDPR settings
545 + }
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 +
235 706 /**
236 707 * Get misc settings array
237 708 *
238 709 * @return array
@@ -238,9 +709,9 @@
238 709 * @return array
239 710 */
240 711 public function get_general_misc_setting() {
241 712
242 - return apply_filters( 'propertyhive_general_map_settings', array(
713 + return apply_filters( 'propertyhive_general_misc_settings', array(
243 714
244 715 array( 'title' => __( 'Property Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'property_options' ),
245 716
246 717 array(
@@ -250,13 +721,40 @@
250 721 'default' => '=',
251 722 'options' => array(
252 723 '=' => __( 'Match Keyword Exactly', 'propertyhive' ),
253 724 'LIKE' => __( 'Perform Loose Search', 'propertyhive' ),
725 + 'polygon' => __( 'Search Within Location Perimeter', 'propertyhive' ),
254 726 ),
255 - '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' )
256 728 ),
257 729
258 730 array(
731 + 'title' => __( 'Commercial Display In Search Results', 'propertyhive' ),
732 + 'id' => 'propertyhive_commercial_display',
733 + 'type' => 'select',
734 + 'default' => '',
735 + 'css' => 'min-width:300px;',
736 + 'options' => array(
737 + '' => __( 'Display top level properties and units', 'propertyhive' ),
738 + 'top_level_only' => __( 'Display top level properties only', 'propertyhive' ),
739 + 'top_level_only_but_units_when_filtered' => __( 'Display top level properties only but units as well if matching filters', 'propertyhive' ),
740 + ),
741 + ),
742 +
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(
259 757 'title' => __( 'When Entering Features', 'propertyhive' ),
260 758 'id' => 'propertyhive_features_type',
261 759 'type' => 'radio',
262 760 'options' => array(
@@ -262,14 +760,27 @@
262 760 'options' => array(
263 761 '' => __( 'Allow Features To Be Freetyped', 'propertyhive' ),
264 762 'checkbox' => __( 'Select From A Predefined List (Editable from \'Custom Fields\')', 'propertyhive' ),
265 763 ),
266 - 'desc' => __( '', 'propertyhive' )
267 764 ),
268 765
766 + array(
767 + 'title' => __( 'Enable Auto-Incremental Reference Numbers', 'propertyhive' ),
768 + 'id' => 'propertyhive_auto_incremental_reference_numbers',
769 + 'type' => 'checkbox',
770 + 'desc' => __( 'Will prefill the property reference with an auto-incremental number when adding a new property', 'propertyhive' )
771 + ),
772 +
773 + array(
774 + 'title' => __( 'Next Incremental Reference Number', 'propertyhive' ),
775 + 'id' => 'propertyhive_auto_incremental_next',
776 + 'type' => 'number',
777 + 'default' => 1,
778 + ),
779 +
269 780 array( 'type' => 'sectionend', 'id' => 'property_options'),
270 781
271 - array( 'title' => __( 'Applicant Registration Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'applicant_registration_options' ),
782 + array( 'title' => __( 'Applicant Options', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'applicant_registration_options' ),
272 783
273 784 array(
274 785 'title' => __( 'Allow Applicants To Login and Manage Account', 'propertyhive' ),
275 786 'id' => 'propertyhive_applicant_users',
@@ -289,13 +800,78 @@
289 800 'id' => 'propertyhive_my_account_page_id',
290 801 'type' => 'single_select_page',
291 802 'default' => '',
292 803 'css' => 'min-width:300px;',
293 - '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' ),
294 805 ),
295 806
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(
835 + 'title' => __( 'Match Price Range % (Lower)', 'propertyhive' ),
836 + 'id' => 'propertyhive_applicant_match_price_range_percentage_lower',
837 + 'type' => 'number',
838 + 'default' => '20',
839 + 'desc' => '<p>' . __( 'When a maximum price is entered against an applicants requirements we create a \'Match Price Range\' which determines the price of properties that would get matched to the applicant.<br>We do this because you:<br>a) Wouldn\'t want to send them properties that were way below their specified maximum price and<br>b) Would probably want to send them properties which are slightly just over the price specified as, if the perfect property came along, a lot of people would actually be able to increase their maximum.<br>These settings determine the % above and below the maximum price entered that the \'Match Price Range\' will default to per applicant.<br>For example, if the maximum price entered was &pound;500,000, the lower setting was 20% and the higher setting was 5%, the applicant would actually match to properties that were priced &pound;400,000 - &pound;525,000.<br>This price range can be overwritten on a per-applicant basis, or disabled completely by leaving these settings empty.<br>Note that this currently only applies to applicants looking for sales properties and changing this value at a later date will only effect applicants that don\'t already have a match price range set.', 'propertyhive' ) . '</p>',
840 + ),
841 +
842 + array(
843 + 'title' => __( 'Match Price Range % (Higher)', 'propertyhive' ),
844 + 'id' => 'propertyhive_applicant_match_price_range_percentage_higher',
845 + 'type' => 'number',
846 + 'default' => '5',
847 + ),
848 +
849 + array(
850 + 'title' => __( 'When Selecting Applicant Locations', 'propertyhive' ),
851 + 'id' => 'propertyhive_applicant_locations_type',
852 + 'type' => 'radio',
853 + 'options' => array(
854 + 'text' => __( 'Allow Locations To Be Freetyped', 'propertyhive' ),
855 + '' => __( 'Select From A Predefined List (Editable from \'Custom Fields\')', 'propertyhive' ),
856 + ),
857 + 'desc' => '<i>' . __( 'When this setting is changed, locations selected using the other method will no longer be used for matching', 'propertyhive' ) . '</i>',
858 + ),
859 +
296 860 array( 'type' => 'sectionend', 'id' => 'applicant_registration_options'),
297 861
862 + array( 'title' => __( 'Improve Property Hive', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'stat_tracking_options' ),
863 +
864 + array(
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' )
870 + ),
871 +
872 + array( 'type' => 'sectionend', 'id' => 'stat_tracking_options'),
873 +
298 874 ) ); // End general misc settings
299 875 }
300 876
301 877 /**
@@ -310,8 +886,12 @@
310 886 {
311 887 case "modules": { $settings = $this->get_general_modules_setting(); break; }
312 888 case "international": { $settings = $this->get_general_international_setting(); break; }
313 889 case "map": { $settings = $this->get_general_map_setting(); break; }
890 + case "media": { $settings = $this->get_general_media_setting(); break; }
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; }
314 894 case "misc": { $settings = $this->get_general_misc_setting(); break; }
315 895 default: { die("Unknown setting section"); }
316 896 }
317 897 }
@@ -333,9 +913,9 @@
333 913 * @param string $desc (default: '')
334 914 * @return void
335 915 */
336 916 function color_picker( $name, $id, $value, $desc = '' ) {
337 - 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>
338 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>
339 919 </div>';
340 920 }
341 921
@@ -342,8 +922,12 @@
342 922 /**
343 923 * Save settings
344 924 */
345 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 +
346 930 global $current_section;
347 931
348 932 if ( $current_section != '' )
349 933 {
@@ -355,31 +939,48 @@
355 939
356 940 PH_Admin_Settings::save_fields( $settings );
357 941 break;
358 942 }
359 - case 'international':
360 - {
361 - if (!isset($_POST['propertyhive_countries']) || (isset($_POST['propertyhive_countries']) && empty($_POST['propertyhive_countries'])))
362 - {
363 - // If we haven't selected which countries we operate in
364 - update_option( 'propertyhive_countries', array( $_POST['propertyhive_default_country'] ) );
365 - }
366 - else
367 - {
368 - // We have default country and countries set
369 - // Make sure default country is in list of countries selected
370 - if ( !in_array($_POST['propertyhive_default_country'], $_POST['propertyhive_countries']) ) {
371 - $_POST['propertyhive_default_country'] = $_POST['propertyhive_countries'][0];
372 - }
373 -
374 - update_option( 'propertyhive_default_country', $_POST['propertyhive_default_country'] );
375 - update_option( 'propertyhive_countries', $_POST['propertyhive_countries'] );
376 - }
377 -
378 - do_action( 'propertyhive_update_currency_exchange_rates' );
379 -
380 - break;
381 - }
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 + }
382 983 case 'map':
383 984 {
384 985 $settings = $this->get_general_map_setting();
385 986
@@ -385,8 +986,71 @@
385 986
386 987 PH_Admin_Settings::save_fields( $settings );
387 988 break;
388 989 }
990 + case 'media':
991 + {
992 + $settings = $this->get_general_media_setting();
993 +
994 + PH_Admin_Settings::save_fields( $settings );
995 + break;
996 + }
997 + case 'gdpr':
998 + {
999 + $settings = $this->get_general_gdpr_setting();
1000 +
1001 + PH_Admin_Settings::save_fields( $settings );
1002 + break;
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 + }
389 1053 case 'misc':
390 1054 {
391 1055 $settings = $this->get_general_misc_setting();
392 1056
@@ -397,13 +1061,149 @@
397 1061 }
398 1062 }
399 1063 else
400 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 +
401 1085 $settings = $this->get_settings();
402 1086
403 1087 PH_Admin_Settings::save_fields( $settings );
404 1088
405 - 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 );
406 1206 }
407 1207 }
408 1208
409 1209 }