PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
← All changes | includes/admin/class-ph-admin-settings.php +322 -123 1.4.52.3.0 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 Admin Settings Class.
4 7 *
5 8 * @author PropertyHive
@@ -14,8 +17,9 @@
14 17
15 18 /**
16 19 * PH_Admin_Settings
17 20 */
21 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Settings; preserving the existing PH_* class name is required for plugin and extension compatibility.
18 22 class PH_Admin_Settings {
19 23
20 24 private static $settings = array();
21 25 private static $errors = array();
@@ -32,12 +36,23 @@
32 36
33 37 $settings[] = include( 'settings/class-ph-settings-general.php' );
34 38 $settings[] = include( 'settings/class-ph-settings-offices.php' );
35 39 $settings[] = include( 'settings/class-ph-settings-custom-fields.php' );
40 + $settings[] = include( 'settings/class-ph-settings-frontend.php' );
36 41 $settings[] = include( 'settings/class-ph-settings-emails.php' );
42 + $settings[] = include( 'settings/class-ph-settings-features.php' );
37 43 $settings[] = include( 'settings/class-ph-settings-licenses.php' );
38 - $settings[] = include( 'settings/class-ph-settings-add-ons.php' );
39 44
45 + // Only show demo data tab if demo data add on not active, tab not dismissed and if newly installed since 2021-04-13 00:00:00
46 + if (
47 + !class_exists('PH_Demo_Data') &&
48 + get_option( 'propertyhive_install_timestamp', '' ) >= 1618268400 &&
49 + get_option( 'propertyhive_hide_demo_data_tab', '' ) != 'yes'
50 + )
51 + {
52 + $settings[] = include( 'settings/class-ph-settings-demo-data.php' );
53 + }
54 +
40 55 self::$settings = apply_filters( 'propertyhive_get_settings_pages', $settings );
41 56 }
42 57 return self::$settings;
43 58 }
@@ -47,27 +62,23 @@
47 62 */
48 63 public static function save() {
49 64 global $current_section, $current_tab;
50 65
51 - if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'propertyhive-settings' ) )
52 - die( __( 'Action failed. Please refresh the page and retry.', 'propertyhive' ) );
66 + if ( ! current_user_can( 'manage_options' ) ) {
67 + wp_die( esc_html__( 'Insufficient permissions', 'propertyhive' ), '', array( 'response' => 403 ) );
68 + }
53 69
70 + if ( empty( $_REQUEST['_wpnonce'] ) || ! is_string( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'propertyhive-settings' ) )
71 + die( esc_html(__( 'Action failed. Please refresh the page and retry.', 'propertyhive' )) );
72 +
54 73 // Trigger actions
55 74 do_action( 'propertyhive_settings_save_' . $current_tab );
56 75 do_action( 'propertyhive_update_options_' . $current_tab );
57 76 do_action( 'propertyhive_update_options' );
58 77
59 - // Clear any unwanted data
60 - //ph_delete_property_transients();
61 - //delete_transient( 'propertyhive_cache_excluded_uris' );
62 -
63 78 self::add_message( __( 'Your settings have been saved.', 'propertyhive' ) );
64 - //self::check_download_folder_protection();
65 79
66 - // Re-add endpoints and flush rules
67 - //PH()->query->init_query_vars();
68 - //PH()->query->add_endpoints();
69 - flush_rewrite_rules();
80 + update_option( 'propertyhive_queue_flush_rewrite_rules', 'yes' );
70 81
71 82 do_action( 'propertyhive_settings_saved' );
72 83 }
73 84
@@ -92,12 +103,32 @@
92 103 */
93 104 public static function show_messages() {
94 105 if ( sizeof( self::$errors ) > 0 ) {
95 106 foreach ( self::$errors as $error )
96 - echo '<div id="message" class="error fade"><p><strong>' . $error . '</strong></p></div>';
107 + {
108 + $allowed_tags = array(
109 + 'a' => array(
110 + 'href' => array(),
111 + ),
112 + );
113 +
114 + $error = wp_kses($error, $allowed_tags);
115 +
116 + echo '<div id="message" class="error fade"><p><strong>' . wp_kses( $error, $allowed_tags ) . '</strong></p></div>';
117 + }
97 118 } elseif ( sizeof( self::$messages ) > 0 ) {
98 119 foreach ( self::$messages as $message )
99 - echo '<div id="message" class="updated fade"><p><strong>' . $message . '</strong></p></div>';
120 + {
121 + $allowed_tags = array(
122 + 'a' => array(
123 + 'href' => array(),
124 + ),
125 + );
126 +
127 + $message = wp_kses($message, $allowed_tags);
128 +
129 + echo '<div id="message" class="updated fade"><p><strong>' . wp_kses( $message, $allowed_tags ) . '</strong></p></div>';
130 + }
100 131 }
101 132 }
102 133
103 134 /**
@@ -108,9 +139,9 @@
108 139 * @access public
109 140 * @return void
110 141 */
111 142 public static function output() {
112 - global $current_section, $current_tab;
143 + global $current_section, $current_tab, $redirect_after_save;
113 144
114 145 do_action( 'propertyhive_settings_start' );
115 146
116 147 //wp_enqueue_script( 'propertyhive_settings', PH()->plugin_url() . '/assets/js/admin/settings.min.js', array( 'jquery'/*, 'jquery-ui-datepicker', 'jquery-ui-sortable', 'iris', 'chosen'*/ ), PH()->version, true );
@@ -122,21 +153,32 @@
122 153 // Include settings pages
123 154 self::get_settings_pages();
124 155
125 156 // Get current tab/section
126 - $current_tab = empty( $_GET['tab'] ) ? 'general' : sanitize_title( $_GET['tab'] );
127 - $current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( $_REQUEST['section'] );
157 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- These values only select the read-only settings view; settings writes are handled by save_fields() after the settings nonce and capability checks.
158 + $request_get = wp_unslash( $_GET );
159 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- These values only select the read-only settings view; settings writes are handled by save_fields() after the settings nonce and capability checks.
160 + $request_request = wp_unslash( $_REQUEST );
161 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
162 + $current_tab = ( isset( $request_get['tab'] ) && is_string( $request_get['tab'] ) && '' !== $request_get['tab'] ) ? sanitize_title( $request_get['tab'] ) : 'general';
163 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
164 + $current_section = ( isset( $request_request['section'] ) && is_string( $request_request['section'] ) ) ? sanitize_title( $request_request['section'] ) : '';
128 165
129 166 // Save settings if data has been posted
130 - if ( ! empty( $_POST ) )
131 - self::save();
167 + //if ( ! empty( $_POST ) )
168 + // self::save();
132 169
133 170 // Add any posted messages
134 - if ( ! empty( $_GET['ph_error'] ) )
135 - self::add_error( stripslashes( $_GET['ph_error'] ) );
171 + $message_allowed_tags = array(
172 + 'a' => array(
173 + 'href' => array(),
174 + ),
175 + );
176 + if ( isset( $request_get['ph_error'] ) && is_scalar( $request_get['ph_error'] ) && '' !== (string) $request_get['ph_error'] )
177 + self::add_error( wp_kses( (string) $request_get['ph_error'], $message_allowed_tags ) );
136 178
137 - if ( ! empty( $_GET['ph_message'] ) )
138 - self::add_message( stripslashes( $_GET['ph_message'] ) );
179 + if ( isset( $request_get['ph_message'] ) && is_scalar( $request_get['ph_message'] ) && '' !== (string) $request_get['ph_message'] )
180 + self::add_message( wp_kses( (string) $request_get['ph_message'], $message_allowed_tags ) );
139 181
140 182 self::show_messages();
141 183
142 184 // Get tabs for the settings page
@@ -232,13 +274,13 @@
232 274 }
233 275
234 276 if ( $tip && in_array( $value['type'], array( 'checkbox' ) ) ) {
235 277
236 - $tip = '<p class="description">' . $tip . '</p>';
278 + $tip = '<p class="description">' . wp_kses_post( $tip ) . '</p>';
237 279
238 280 } elseif ( $tip ) {
239 281
240 - $tip = '<img class="help_tip" data-tip="' . esc_attr( $tip ) . '" src="' . PH()->plugin_url() . '/assets/images/help.png" height="16" width="16" />';
282 + $tip = '<img class="help_tip" data-tip="' . esc_attr( wp_kses_post( $tip ) ) . '" src="' . esc_url( PH()->plugin_url() . '/assets/images/help.png' ) . '" height="16" width="16" />';
241 283
242 284 }
243 285
244 286 // Switch based on type
@@ -249,9 +291,9 @@
249 291 if ( ! empty( $value['title'] ) ) {
250 292 echo '<h3>' . esc_html( $value['title'] ) . '</h3>';
251 293 }
252 294 if ( ! empty( $value['desc'] ) ) {
253 - echo wpautop( wptexturize( wp_kses_post( $value['desc'] ) ) );
295 + echo wp_kses_post( wpautop( wptexturize( wp_kses_post( $value['desc'] ) ) ) );
254 296 }
255 297 echo '<table class="form-table">'. "\n\n";
256 298 if ( ! empty( $value['id'] ) ) {
257 299 do_action( 'propertyhive_settings_' . sanitize_title( $value['id'] ) );
@@ -269,16 +311,83 @@
269 311 }
270 312 break;
271 313
272 314 case 'html':
315 + $full_width = ( isset($value['full_width']) && is_bool($value['full_width']) ) ? $value['full_width'] : false;
273 316 ?>
274 - <tr valign="top">
317 + <tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
318 + <?php if ( $full_width !== true ) { ?>
275 319 <th scope="row" class="titledesc">
276 320 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
277 - <?php echo $tip; ?>
321 + <?php echo wp_kses_post($tip); ?>
278 322 </th>
279 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
280 - <?php echo $value['html']; ?>
323 + <?php } ?>
324 + <td class="forminp forminp-<?php echo esc_attr(sanitize_title( $value['type'] )); ?>">
325 + <?php
326 + $allowed_html = wp_kses_allowed_html( 'post' );
327 +
328 + $allowed_html['fieldset'] = array(
329 + 'id' => true,
330 + 'class' => true,
331 + );
332 +
333 + $allowed_html['legend'] = array(
334 + 'class' => true,
335 + );
336 +
337 + $allowed_html['label'] = array(
338 + 'for' => true,
339 + 'class' => true,
340 + );
341 +
342 + $allowed_html['input'] = array(
343 + 'type' => true,
344 + 'name' => true,
345 + 'id' => true,
346 + 'value' => true,
347 + 'class' => true,
348 + 'style' => true,
349 + 'checked' => true,
350 + 'disabled' => true,
351 + 'placeholder' => true,
352 + );
353 +
354 + $allowed_html['select'] = array(
355 + 'name' => true,
356 + 'id' => true,
357 + 'class' => true,
358 + 'style' => true,
359 + 'multiple' => true,
360 + 'disabled' => true,
361 + );
362 +
363 + $allowed_html['option'] = array(
364 + 'value' => true,
365 + 'selected' => true,
366 + 'disabled' => true,
367 + );
368 +
369 + /**
370 + * Scripts are permitted for backward compatibility because existing
371 + * Property Hive extensions use HTML settings fields to output inline
372 + * administration scripts. To be revised in future after mentioned
373 + * extensions have been updated
374 + */
375 + $allowed_html['script'] = array(
376 + 'type' => true,
377 + 'src' => true,
378 + );
379 +
380 + $allowed_html['a']['data-department'] = true;
381 +
382 + $allowed_html = apply_filters(
383 + 'propertyhive_admin_settings_html_allowed_tags',
384 + $allowed_html,
385 + $value
386 + );
387 +
388 + echo wp_kses( $value['html'], $allowed_html );
389 + ?>
281 390 </td>
282 391 </tr>
283 392 <?php
284 393 break;
@@ -299,14 +408,14 @@
299 408 $value['class'] .= 'colorpick';
300 409 $description .= '<div id="colorPickerDiv_' . esc_attr( $value['id'] ) . '" class="colorpickdiv" style="z-index: 100;background:#eee;border:1px solid #ccc;position:absolute;display:none;"></div>';
301 410 }
302 411
303 - ?><tr valign="top">
412 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
304 413 <th scope="row" class="titledesc">
305 414 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
306 - <?php echo $tip; ?>
415 + <?php echo wp_kses_post( $tip ); ?>
307 416 </th>
308 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
417 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
309 418 <input
310 419 name="<?php echo esc_attr( $value['id'] ); ?>"
311 420 id="<?php echo esc_attr( $value['id'] ); ?>"
312 421 type="<?php echo esc_attr( $type ); ?>"
@@ -312,10 +421,13 @@
312 421 type="<?php echo esc_attr( $type ); ?>"
313 422 style="<?php echo esc_attr( $value['css'] ); ?>"
314 423 value="<?php echo esc_attr( $option_value ); ?>"
315 424 class="<?php echo esc_attr( $value['class'] ); ?>"
316 - <?php echo implode( ' ', $custom_attributes ); ?>
317 - /> <?php echo $description; ?>
425 + <?php
426 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
427 + echo implode( ' ', $custom_attributes );
428 + ?>
429 + /> <?php echo wp_kses_post( $description ); ?>
318 430 </td>
319 431 </tr><?php
320 432 break;
321 433
@@ -335,15 +447,15 @@
335 447 case 'textarea':
336 448
337 449 $option_value = self::get_option( $value['id'], $value['default'] );
338 450
339 - ?><tr valign="top">
451 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
340 452 <th scope="row" class="titledesc">
341 453 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
342 - <?php echo $tip; ?>
454 + <?php echo wp_kses_post( $tip ); ?>
343 455 </th>
344 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
345 - <?php echo $description; ?>
456 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
457 + <?php echo wp_kses_post( $description ); ?>
346 458
347 459 <textarea
348 460 name="<?php echo esc_attr( $value['id'] ); ?>"
349 461 id="<?php echo esc_attr( $value['id'] ); ?>"
@@ -348,14 +460,45 @@
348 460 name="<?php echo esc_attr( $value['id'] ); ?>"
349 461 id="<?php echo esc_attr( $value['id'] ); ?>"
350 462 style="<?php echo esc_attr( $value['css'] ); ?>"
351 463 class="<?php echo esc_attr( $value['class'] ); ?>"
352 - <?php echo implode( ' ', $custom_attributes ); ?>
464 + <?php
465 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
466 + echo implode( ' ', $custom_attributes );
467 + ?>
353 468 ><?php echo esc_textarea( $option_value ); ?></textarea>
354 469 </td>
355 470 </tr><?php
356 471 break;
357 472
473 + // WYSIWYG
474 + case 'wysiwyg':
475 +
476 + $option_value = self::get_option( $value['id'], $value['default'] );
477 +
478 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
479 + <th scope="row" class="titledesc">
480 + <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
481 + <?php echo wp_kses_post( $tip ); ?>
482 + </th>
483 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
484 +
485 + <?php wp_editor( $option_value, esc_attr( $value['id'] ), array( 'media_buttons' => false, 'textarea_rows' => 3, 'teeny' => true ) ); ?>
486 +
487 + <?php echo '<br>' . wp_kses_post( $description ); ?>
488 +
489 + <?php /*<textarea
490 + name="<?php echo esc_attr( $value['id'] ); ?>"
491 + id="<?php echo esc_attr( $value['id'] ); ?>"
492 + <?php
493 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
494 + echo implode( ' ', $custom_attributes );
495 + ?>
496 + ><?php echo esc_textarea( $option_value ); ?></textarea>*/ ?>
497 + </td>
498 + </tr><?php
499 + break;
500 +
358 501 // Select boxes
359 502 case 'select' :
360 503 case 'multiselect' :
361 504
@@ -360,20 +503,23 @@
360 503 case 'multiselect' :
361 504
362 505 $option_value = self::get_option( $value['id'], $value['default'] );
363 506
364 - ?><tr valign="top">
507 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
365 508 <th scope="row" class="titledesc">
366 509 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
367 - <?php echo $tip; ?>
510 + <?php echo wp_kses_post( $tip ); ?>
368 511 </th>
369 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
512 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
370 513 <select
371 514 name="<?php echo esc_attr( $value['id'] ); ?><?php if ( $value['type'] == 'multiselect' ) echo '[]'; ?>"
372 515 id="<?php echo esc_attr( $value['id'] ); ?>"
373 516 style="<?php echo esc_attr( $value['css'] ); ?>"
374 517 class="<?php echo esc_attr( $value['class'] ); ?>"
375 - <?php echo implode( ' ', $custom_attributes ); ?>
518 + <?php
519 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
520 + echo implode( ' ', $custom_attributes );
521 + ?>
376 522 <?php if ( $value['type'] == 'multiselect' ) echo 'multiple="multiple"'; ?>
377 523 >
378 524 <?php
379 525 foreach ( $value['options'] as $key => $val ) {
@@ -384,13 +530,13 @@
384 530 selected( in_array( $key, $option_value ), true );
385 531 else
386 532 selected( $option_value, $key );
387 533
388 - ?>><?php echo $val ?></option>
534 + ?>><?php echo esc_html( $val ); ?></option>
389 535 <?php
390 536 }
391 537 ?>
392 - </select> <?php echo $description; ?>
538 + </select> <?php echo wp_kses_post( $description ); ?>
393 539 </td>
394 540 </tr><?php
395 541 break;
396 542
@@ -398,16 +544,16 @@
398 544 case 'radio' :
399 545
400 546 $option_value = self::get_option( $value['id'], $value['default'] );
401 547
402 - ?><tr valign="top">
548 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
403 549 <th scope="row" class="titledesc">
404 550 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
405 - <?php echo $tip; ?>
551 + <?php echo wp_kses_post( $tip ); ?>
406 552 </th>
407 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
553 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
408 554 <fieldset>
409 - <?php echo $description; ?>
555 + <?php echo wp_kses_post( $description ); ?>
410 556 <ul>
411 557 <?php
412 558 foreach ( $value['options'] as $key => $val ) {
413 559 ?>
@@ -413,15 +559,18 @@
413 559 ?>
414 560 <li>
415 561 <label><input
416 562 name="<?php echo esc_attr( $value['id'] ); ?>"
417 - value="<?php echo $key; ?>"
563 + value="<?php echo esc_attr( $key ); ?>"
418 564 type="radio"
419 565 style="<?php echo esc_attr( $value['css'] ); ?>"
420 566 class="<?php echo esc_attr( $value['class'] ); ?>"
421 - <?php echo implode( ' ', $custom_attributes ); ?>
567 + <?php
568 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
569 + echo implode( ' ', $custom_attributes );
570 + ?>
422 571 <?php checked( $key, $option_value ); ?>
423 - /> <?php echo $val ?></label>
572 + /> <?php echo wp_kses_post( $val ); ?></label>
424 573 </li>
425 574 <?php
426 575 }
427 576 ?>
@@ -433,9 +582,12 @@
433 582
434 583 // Checkbox input
435 584 case 'checkbox' :
436 585
437 - $option_value = self::get_option( $value['id'], $value['default'] );
586 + $name = isset($value['name']) && $value['name'] != '' ? ph_clean($value['name']) : $value['id'];
587 + $option_value = isset($value['value']) ? ph_clean($value['value']) : self::get_option( $value['id'], $value['default'] );
588 + $fieldset_css = isset($value['fieldset_css']) ? ph_clean($value['fieldset_css']) : '';
589 +
438 590 $visbility_class = array();
439 591
440 592 if ( ! isset( $value['hide_if_checked'] ) ) {
441 593 $value['hide_if_checked'] = false;
@@ -454,16 +606,16 @@
454 606 }
455 607
456 608 if ( ! isset( $value['checkboxgroup'] ) || 'start' == $value['checkboxgroup'] ) {
457 609 ?>
458 - <tr valign="top" class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>">
610 + <tr valign="top" class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>" id="row_<?php echo esc_attr( $value['id'] ); ?>">
459 611 <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?></th>
460 612 <td class="forminp forminp-checkbox">
461 - <fieldset>
613 + <fieldset style="<?php echo esc_attr( $fieldset_css ); ?>">
462 614 <?php
463 - } else {
615 + } else {
464 616 ?>
465 - <fieldset class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>">
617 + <fieldset style="<?php echo esc_attr( $fieldset_css ); ?>" class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>">
466 618 <?php
467 619 }
468 620
469 621 if ( ! empty( $value['title'] ) ) {
@@ -472,18 +624,21 @@
472 624 <?php
473 625 }
474 626
475 627 ?>
476 - <label for="<?php echo $value['id'] ?>">
628 + <label for="<?php echo esc_attr( $value['id'] ); ?>">
477 629 <input
478 - name="<?php echo esc_attr( $value['id'] ); ?>"
630 + name="<?php echo esc_attr( $name ); ?>"
479 631 id="<?php echo esc_attr( $value['id'] ); ?>"
480 632 type="checkbox"
481 633 value="1"
482 634 <?php checked( $option_value, 'yes'); ?>
483 - <?php echo implode( ' ', $custom_attributes ); ?>
484 - /> <?php echo $description ?>
485 - </label> <?php echo $tip; ?>
635 + <?php
636 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Every custom attribute name and value is escaped when assembled above; retain trusted PHP settings attributes.
637 + echo implode( ' ', $custom_attributes );
638 + ?>
639 + /> <?php echo wp_kses_post( $description ); ?>
640 + </label> <?php echo wp_kses_post( $tip ); ?>
486 641 <?php
487 642
488 643 if ( ! isset( $value['checkboxgroup'] ) || 'end' == $value['checkboxgroup'] ) {
489 644 ?>
@@ -502,22 +657,82 @@
502 657 case 'image_width' :
503 658
504 659 $width = self::get_option( $value['id'] . '[width]', $value['default']['width'] );
505 660 $height = self::get_option( $value['id'] . '[height]', $value['default']['height'] );
506 - $crop = checked( 1, self::get_option( $value['id'] . '[crop]', $value['default']['crop'] ), false );
507 661
508 - ?><tr valign="top">
509 - <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo $tip; ?></th>
662 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
663 + <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo wp_kses_post( $tip ); ?></th>
510 664 <td class="forminp image_width_settings">
511 665
512 - <input name="<?php echo esc_attr( $value['id'] ); ?>[width]" id="<?php echo esc_attr( $value['id'] ); ?>-width" type="text" size="3" value="<?php echo $width; ?>" /> &times; <input name="<?php echo esc_attr( $value['id'] ); ?>[height]" id="<?php echo esc_attr( $value['id'] ); ?>-height" type="text" size="3" value="<?php echo $height; ?>" />px
666 + <input name="<?php echo esc_attr( $value['id'] ); ?>[width]" id="<?php echo esc_attr( $value['id'] ); ?>-width" type="text" size="3" value="<?php echo esc_attr( $width ); ?>" /> &times; <input name="<?php echo esc_attr( $value['id'] ); ?>[height]" id="<?php echo esc_attr( $value['id'] ); ?>-height" type="text" size="3" value="<?php echo esc_attr( $height ); ?>" />px
513 667
514 - <label><input name="<?php echo esc_attr( $value['id'] ); ?>[crop]" id="<?php echo esc_attr( $value['id'] ); ?>-crop" type="checkbox" <?php echo $crop; ?> /> <?php _e( 'Hard Crop?', 'propertyhive' ); ?></label>
668 + <label><input name="<?php echo esc_attr( $value['id'] ); ?>[crop]" id="<?php echo esc_attr( $value['id'] ); ?>-crop" type="checkbox" <?php checked( 1, self::get_option( $value['id'] . '[crop]', $value['default']['crop'] ) ); ?> /> <?php esc_html_e( 'Hard Crop?', 'propertyhive' ); ?></label>
515 669
516 670 </td>
517 671 </tr><?php
518 672 break;
519 673
674 + // Image
675 + case 'image' :
676 +
677 + $option_value = self::get_option( $value['id'], $value['default'] );
678 +
679 + ?>
680 + <tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>_uploaded" <?php if ( $option_value == '' ) { echo ' style="display:none"'; } ?>>
681 + <th scope="row" class="titledesc"><?php echo esc_html( __( 'Uploaded', 'propertyhive' ) . ' ' . $value['title'] ); ?></th>
682 + <td class="forminp image_settings">
683 + <?php
684 + $image = wp_get_attachment_image_src( $option_value, 'thumbnail' );
685 + if ($image !== FALSE)
686 + {
687 + echo '<img src="' . esc_url( $image[0] ) . '" width="150" alt="">';
688 + }
689 + else
690 + {
691 + echo 'Image doesn\'t exist';
692 + }
693 + ?>
694 + </td>
695 + </tr>
696 + <tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
697 + <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo wp_kses_post( $tip ); ?></th>
698 + <td class="forminp image_settings">
699 +
700 + <a href="" data-ph-image-field="<?php echo esc_attr( $value['id'] ); ?>" class="button button-primary ph_upload_photo_button<?php echo esc_attr( $value['id'] ); ?>">Select Image</a>
701 + <input name="<?php echo esc_attr( $value['id'] ); ?>" id="<?php echo esc_attr( $value['id'] ); ?>" type="hidden" value="<?php echo esc_attr( $option_value ); ?>" />
702 +
703 + </td>
704 + </tr><?php
705 + echo '<script>
706 +(function(fieldId) {
707 + jQuery(function($) {
708 + $(document.body).on("click", "[data-ph-image-field]", function(event) {
709 + if ($(this).attr("data-ph-image-field") !== fieldId) { return; }
710 + event.preventDefault();
711 + var frameKey = "file_frame" + fieldId;
712 + var frame = wp.media.frames[frameKey] || window[frameKey];
713 + if (frame) { frame.open(); return; }
714 + frame = wp.media({
715 + title: $(this).data("uploader_title"),
716 + button: { text: $(this).data("uploader_button_text") },
717 + multiple: false
718 + });
719 + wp.media.frames[frameKey] = window[frameKey] = frame;
720 + frame.on("select", function() {
721 + frame.state().get("selection").map(function(attachment) {
722 + attachment = attachment.toJSON();
723 + var row = $(document.getElementById("row_" + fieldId + "_uploaded"));
724 + row.show().find("td").empty().append($("<img>", { src: attachment.url, width: 150, alt: "" }));
725 + $(document.getElementById(fieldId)).val(attachment.id);
726 + });
727 + });
728 + frame.open();
729 + });
730 + });
731 +})(' . wp_json_encode( (string) $value['id'], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ');
732 +</script>';
733 + break;
734 +
520 735 // Single page selects
521 736 case 'single_select_page' :
522 737
523 738 $args = array( 'name' => $value['id'],
@@ -532,12 +747,14 @@
532 747
533 748 if( isset( $value['args'] ) )
534 749 $args = wp_parse_args( $value['args'], $args );
535 750
536 - ?><tr valign="top" class="single_select_page">
537 - <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo $tip; ?></th>
751 + ?><tr valign="top" class="single_select_page" id="row_<?php echo esc_attr( $value['id'] ); ?>">
752 + <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo wp_kses_post( $tip ); ?></th>
538 753 <td class="forminp">
539 - <?php echo str_replace(' id=', " data-placeholder='" . __( 'Select a page&hellip;', 'propertyhive' ) . "' style='" . $value['css'] . "' class='" . $value['class'] . "' id=", wp_dropdown_pages( $args ) ); ?> <?php echo $description; ?>
754 + <?php
755 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_dropdown_pages() produces escaped select HTML; all inserted attribute values are escaped here and trusted core filters retain their HTML contract.
756 + echo str_replace(' id=', " data-placeholder='" . esc_attr__( 'Select a page&hellip;', 'propertyhive' ) . "' style='" . esc_attr( $value['css'] ) . "' class='" . esc_attr( $value['class'] ) . "' id=", wp_dropdown_pages( $args ) ); ?> <?php echo wp_kses_post( $description ); ?>
540 757 </td>
541 758 </tr><?php
542 759 break;
543 760
@@ -551,18 +768,18 @@
551 768 $country = current( $country_setting );
552 769 } else {
553 770 $country = $country_setting;
554 771 }
555 - ?><tr valign="top">
772 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
556 773 <th scope="row" class="titledesc">
557 774 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
558 - <?php echo $tip; ?>
775 + <?php echo wp_kses_post( $tip ); ?>
559 776 </th>
560 777 <td class="forminp">
561 778 <select name="<?php echo esc_attr( $value['id'] ); ?>" style="<?php echo esc_attr( $value['css'] ); ?>">
562 779 <?php PH()->countries->country_dropdown_options( $country ); ?>
563 780 </select>
564 - <?php echo $description; ?>
781 + <?php echo wp_kses_post( $description ); ?>
565 782 </td>
566 783 </tr><?php
567 784 break;
568 785
@@ -576,12 +793,12 @@
576 793 else
577 794 $countries = PH()->countries->countries;
578 795
579 796 asort( $countries );
580 - ?><tr valign="top">
797 + ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
581 798 <th scope="row" class="titledesc">
582 799 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
583 - <?php echo $tip; ?>
800 + <?php echo wp_kses_post( $tip ); ?>
584 801 </th>
585 802 <td class="forminp">
586 803 <select multiple="multiple" name="<?php echo esc_attr( $value['id'] ); ?>[]" style="<?php echo esc_attr( $value['css'] ); ?>">
587 804 <?php
@@ -586,11 +803,11 @@
586 803 <select multiple="multiple" name="<?php echo esc_attr( $value['id'] ); ?>[]" style="<?php echo esc_attr( $value['css'] ); ?>">
587 804 <?php
588 805 if ( $countries )
589 806 foreach ( $countries as $key => $val )
590 - echo '<option value="' . esc_attr( $key ) . '" ' . selected( in_array( $key, $selections ), true, false ).'>' . $val['name'] . '</option>';
807 + echo '<option value="' . esc_attr( $key ) . '" ' . selected( in_array( $key, $selections ), true, false ).'>' . esc_html( $val['name'] ) . '</option>';
591 808 ?>
592 - </select> <?php if ( $description ) echo $description; ?>
809 + </select> <?php if ( $description ) echo wp_kses_post( $description ); ?>
593 810 </td>
594 811 </tr><?php
595 812 break;
596 813
@@ -611,11 +828,18 @@
611 828 * @param array $options Opens array to output
612 829 * @return bool
613 830 */
614 831 public static function save_fields( $options ) {
832 + 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' ) ) {
833 + return;
834 + }
835 +
615 836 if ( empty( $_POST ) )
616 837 return false;
617 838
839 + // The settings nonce and manage_options capability were verified above.
840 + $request_post = wp_unslash( $_POST );
841 +
618 842 // Options to update will be stored here
619 843 $update_options = array();
620 844
621 845 // Loop options and get values to save
@@ -633,9 +857,9 @@
633 857
634 858 // Standard types
635 859 case "checkbox" :
636 860
637 - if ( isset( $_POST[ $value['id'] ] ) ) {
861 + if ( isset( $request_post[ $value['id'] ] ) ) {
638 862 $option_value = 'yes';
639 863 } else {
640 864 $option_value = 'no';
641 865 }
@@ -642,11 +866,12 @@
642 866
643 867 break;
644 868
645 869 case "textarea" :
870 + case "wysiwyg" :
646 871
647 - if ( isset( $_POST[$value['id']] ) ) {
648 - $option_value = wp_kses_post( trim( stripslashes( $_POST[ $value['id'] ] ) ) );
872 + if ( isset( $request_post[$value['id']] ) && is_scalar( $request_post[$value['id']] ) ) {
873 + $option_value = wp_kses_post( trim( $request_post[ $value['id'] ] ) );
649 874 } else {
650 875 $option_value = '';
651 876 }
652 877
@@ -661,10 +886,10 @@
661 886 case "single_select_page" :
662 887 case "single_select_country" :
663 888 case 'radio' :
664 889
665 - if ( isset( $_POST[$value['id']] ) ) {
666 - $option_value = sanitize_text_field( stripslashes( $_POST[ $value['id'] ] ) );
890 + if ( isset( $request_post[$value['id']] ) && is_scalar( $request_post[$value['id']] ) ) {
891 + $option_value = sanitize_text_field( $request_post[ $value['id'] ] );
667 892 } else {
668 893 $option_value = '';
669 894 }
670 895
@@ -673,13 +898,17 @@
673 898 // Special types
674 899 case "multiselect" :
675 900 case "multi_select_countries" :
676 901
677 - // Get countries array
678 - if ( isset( $_POST[ $value['id'] ] ) )
679 - $selected_countries = array_map( 'ph_clean', array_map( 'stripslashes', (array) $_POST[ $value['id'] ] ) );
680 - else
681 - $selected_countries = array();
902 + // Get countries array
903 + $selected_countries = array();
904 + if ( isset( $request_post[ $value['id'] ] ) ) {
905 + foreach ( (array) $request_post[ $value['id'] ] as $selected_country ) {
906 + if ( is_scalar( $selected_country ) ) {
907 + $selected_countries[] = ph_clean( $selected_country );
908 + }
909 + }
910 + }
682 911
683 912 $option_value = $selected_countries;
684 913
685 914 break;
@@ -685,14 +914,15 @@
685 914 break;
686 915
687 916 case "image_width" :
688 917
689 - if ( isset( $_POST[$value['id'] ]['width'] ) ) {
918 + $image_dimensions = ( isset( $request_post[ $value['id'] ] ) && is_array( $request_post[ $value['id'] ] ) ) ? $request_post[ $value['id'] ] : array();
919 + if ( isset( $image_dimensions['width'] ) && is_scalar( $image_dimensions['width'] ) ) {
690 920
691 - $update_options[ $value['id'] ]['width'] = ph_clean( stripslashes( $_POST[ $value['id'] ]['width'] ) );
692 - $update_options[ $value['id'] ]['height'] = ph_clean( stripslashes( $_POST[ $value['id'] ]['height'] ) );
921 + $update_options[ $value['id'] ]['width'] = ph_clean( $image_dimensions['width'] );
922 + $update_options[ $value['id'] ]['height'] = ( isset( $image_dimensions['height'] ) && is_scalar( $image_dimensions['height'] ) ) ? ph_clean( $image_dimensions['height'] ) : $value['default']['height'];
693 923
694 - if ( isset( $_POST[ $value['id'] ]['crop'] ) )
924 + if ( isset( $image_dimensions['crop'] ) )
695 925 $update_options[ $value['id'] ]['crop'] = 1;
696 926 else
697 927 $update_options[ $value['id'] ]['crop'] = 0;
698 928
@@ -749,38 +979,7 @@
749 979 update_option( $name, $value );
750 980
751 981 return true;
752 982 }
753 -
754 - /**
755 - * Checks which method we're using to serve downloads
756 - *
757 - * If using force or x-sendfile, this ensures the .htaccess is in place
758 - *
759 - * @access public
760 - * @return void
761 - */
762 - /*public static function check_download_folder_protection() {
763 - $upload_dir = wp_upload_dir();
764 - $downloads_url = $upload_dir['basedir'] . '/propertyhive_uploads';
765 - $download_method = get_option('propertyhive_file_download_method');
766 -
767 - if ( $download_method == 'redirect' ) {
768 -
769 - // Redirect method - don't protect
770 - if ( file_exists( $downloads_url . '/.htaccess' ) )
771 - unlink( $downloads_url . '/.htaccess' );
772 -
773 - } else {
774 -
775 - // Force method - protect, add rules to the htaccess file
776 - if ( ! file_exists( $downloads_url . '/.htaccess' ) ) {
777 - if ( $file_handle = @fopen( $downloads_url . '/.htaccess', 'w' ) ) {
778 - fwrite( $file_handle, 'deny from all' );
779 - fclose( $file_handle );
780 - }
781 - }
782 - }
783 - }*/
784 983 }
785 984
786 985 endif;