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/class-ph-admin-settings.php +288 -117 1.4.562.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 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 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;
@@ -302,11 +411,11 @@
302 411
303 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
@@ -338,12 +450,12 @@
338 450
339 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,9 +460,12 @@
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;
@@ -362,20 +477,23 @@
362 477
363 478 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
364 479 <th scope="row" class="titledesc">
365 480 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
366 - <?php echo $tip; ?>
481 + <?php echo wp_kses_post( $tip ); ?>
367 482 </th>
368 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
483 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
369 484
370 485 <?php wp_editor( $option_value, esc_attr( $value['id'] ), array( 'media_buttons' => false, 'textarea_rows' => 3, 'teeny' => true ) ); ?>
371 486
372 - <?php echo '<br>' . $description; ?>
487 + <?php echo '<br>' . wp_kses_post( $description ); ?>
373 488
374 489 <?php /*<textarea
375 490 name="<?php echo esc_attr( $value['id'] ); ?>"
376 491 id="<?php echo esc_attr( $value['id'] ); ?>"
377 - <?php echo implode( ' ', $custom_attributes ); ?>
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 + ?>
378 496 ><?php echo esc_textarea( $option_value ); ?></textarea>*/ ?>
379 497 </td>
380 498 </tr><?php
381 499 break;
@@ -388,17 +506,20 @@
388 506
389 507 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
390 508 <th scope="row" class="titledesc">
391 509 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
392 - <?php echo $tip; ?>
510 + <?php echo wp_kses_post( $tip ); ?>
393 511 </th>
394 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
512 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
395 513 <select
396 514 name="<?php echo esc_attr( $value['id'] ); ?><?php if ( $value['type'] == 'multiselect' ) echo '[]'; ?>"
397 515 id="<?php echo esc_attr( $value['id'] ); ?>"
398 516 style="<?php echo esc_attr( $value['css'] ); ?>"
399 517 class="<?php echo esc_attr( $value['class'] ); ?>"
400 - <?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 + ?>
401 522 <?php if ( $value['type'] == 'multiselect' ) echo 'multiple="multiple"'; ?>
402 523 >
403 524 <?php
404 525 foreach ( $value['options'] as $key => $val ) {
@@ -409,13 +530,13 @@
409 530 selected( in_array( $key, $option_value ), true );
410 531 else
411 532 selected( $option_value, $key );
412 533
413 - ?>><?php echo $val ?></option>
534 + ?>><?php echo esc_html( $val ); ?></option>
414 535 <?php
415 536 }
416 537 ?>
417 - </select> <?php echo $description; ?>
538 + </select> <?php echo wp_kses_post( $description ); ?>
418 539 </td>
419 540 </tr><?php
420 541 break;
421 542
@@ -426,13 +547,13 @@
426 547
427 548 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
428 549 <th scope="row" class="titledesc">
429 550 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
430 - <?php echo $tip; ?>
551 + <?php echo wp_kses_post( $tip ); ?>
431 552 </th>
432 - <td class="forminp forminp-<?php echo sanitize_title( $value['type'] ) ?>">
553 + <td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ) ?>">
433 554 <fieldset>
434 - <?php echo $description; ?>
555 + <?php echo wp_kses_post( $description ); ?>
435 556 <ul>
436 557 <?php
437 558 foreach ( $value['options'] as $key => $val ) {
438 559 ?>
@@ -438,15 +559,18 @@
438 559 ?>
439 560 <li>
440 561 <label><input
441 562 name="<?php echo esc_attr( $value['id'] ); ?>"
442 - value="<?php echo $key; ?>"
563 + value="<?php echo esc_attr( $key ); ?>"
443 564 type="radio"
444 565 style="<?php echo esc_attr( $value['css'] ); ?>"
445 566 class="<?php echo esc_attr( $value['class'] ); ?>"
446 - <?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 + ?>
447 571 <?php checked( $key, $option_value ); ?>
448 - /> <?php echo $val ?></label>
572 + /> <?php echo wp_kses_post( $val ); ?></label>
449 573 </li>
450 574 <?php
451 575 }
452 576 ?>
@@ -458,10 +582,11 @@
458 582
459 583 // Checkbox input
460 584 case 'checkbox' :
461 585
462 - $option_value = self::get_option( $value['id'], $value['default'] );
463 - $fieldset_css = isset($value['fieldset_css']) ? ph_clean($value['fieldset_css']) : '';
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']) : '';
464 589
465 590 $visbility_class = array();
466 591
467 592 if ( ! isset( $value['hide_if_checked'] ) ) {
@@ -484,13 +609,13 @@
484 609 ?>
485 610 <tr valign="top" class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>" id="row_<?php echo esc_attr( $value['id'] ); ?>">
486 611 <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?></th>
487 612 <td class="forminp forminp-checkbox">
488 - <fieldset style="<?php echo $fieldset_css; ?>">
613 + <fieldset style="<?php echo esc_attr( $fieldset_css ); ?>">
489 614 <?php
490 615 } else {
491 616 ?>
492 - <fieldset style="<?php echo $fieldset_css; ?>" 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 ) ); ?>">
493 618 <?php
494 619 }
495 620
496 621 if ( ! empty( $value['title'] ) ) {
@@ -499,18 +624,21 @@
499 624 <?php
500 625 }
501 626
502 627 ?>
503 - <label for="<?php echo $value['id'] ?>">
628 + <label for="<?php echo esc_attr( $value['id'] ); ?>">
504 629 <input
505 - name="<?php echo esc_attr( $value['id'] ); ?>"
630 + name="<?php echo esc_attr( $name ); ?>"
506 631 id="<?php echo esc_attr( $value['id'] ); ?>"
507 632 type="checkbox"
508 633 value="1"
509 634 <?php checked( $option_value, 'yes'); ?>
510 - <?php echo implode( ' ', $custom_attributes ); ?>
511 - /> <?php echo $description ?>
512 - </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 ); ?>
513 641 <?php
514 642
515 643 if ( ! isset( $value['checkboxgroup'] ) || 'end' == $value['checkboxgroup'] ) {
516 644 ?>
@@ -529,22 +657,82 @@
529 657 case 'image_width' :
530 658
531 659 $width = self::get_option( $value['id'] . '[width]', $value['default']['width'] );
532 660 $height = self::get_option( $value['id'] . '[height]', $value['default']['height'] );
533 - $crop = checked( 1, self::get_option( $value['id'] . '[crop]', $value['default']['crop'] ), false );
534 661
535 662 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
536 - <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo $tip; ?></th>
663 + <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo wp_kses_post( $tip ); ?></th>
537 664 <td class="forminp image_width_settings">
538 665
539 - <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
540 667
541 - <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>
542 669
543 670 </td>
544 671 </tr><?php
545 672 break;
546 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 +
547 735 // Single page selects
548 736 case 'single_select_page' :
549 737
550 738 $args = array( 'name' => $value['id'],
@@ -560,11 +748,13 @@
560 748 if( isset( $value['args'] ) )
561 749 $args = wp_parse_args( $value['args'], $args );
562 750
563 751 ?><tr valign="top" class="single_select_page" id="row_<?php echo esc_attr( $value['id'] ); ?>">
564 - <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo $tip; ?></th>
752 + <th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?> <?php echo wp_kses_post( $tip ); ?></th>
565 753 <td class="forminp">
566 - <?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 ); ?>
567 757 </td>
568 758 </tr><?php
569 759 break;
570 760
@@ -581,15 +771,15 @@
581 771 }
582 772 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
583 773 <th scope="row" class="titledesc">
584 774 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
585 - <?php echo $tip; ?>
775 + <?php echo wp_kses_post( $tip ); ?>
586 776 </th>
587 777 <td class="forminp">
588 778 <select name="<?php echo esc_attr( $value['id'] ); ?>" style="<?php echo esc_attr( $value['css'] ); ?>">
589 779 <?php PH()->countries->country_dropdown_options( $country ); ?>
590 780 </select>
591 - <?php echo $description; ?>
781 + <?php echo wp_kses_post( $description ); ?>
592 782 </td>
593 783 </tr><?php
594 784 break;
595 785
@@ -606,9 +796,9 @@
606 796 asort( $countries );
607 797 ?><tr valign="top" id="row_<?php echo esc_attr( $value['id'] ); ?>">
608 798 <th scope="row" class="titledesc">
609 799 <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?></label>
610 - <?php echo $tip; ?>
800 + <?php echo wp_kses_post( $tip ); ?>
611 801 </th>
612 802 <td class="forminp">
613 803 <select multiple="multiple" name="<?php echo esc_attr( $value['id'] ); ?>[]" style="<?php echo esc_attr( $value['css'] ); ?>">
614 804 <?php
@@ -613,11 +803,11 @@
613 803 <select multiple="multiple" name="<?php echo esc_attr( $value['id'] ); ?>[]" style="<?php echo esc_attr( $value['css'] ); ?>">
614 804 <?php
615 805 if ( $countries )
616 806 foreach ( $countries as $key => $val )
617 - 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>';
618 808 ?>
619 - </select> <?php if ( $description ) echo $description; ?>
809 + </select> <?php if ( $description ) echo wp_kses_post( $description ); ?>
620 810 </td>
621 811 </tr><?php
622 812 break;
623 813
@@ -638,11 +828,18 @@
638 828 * @param array $options Opens array to output
639 829 * @return bool
640 830 */
641 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 +
642 836 if ( empty( $_POST ) )
643 837 return false;
644 838
839 + // The settings nonce and manage_options capability were verified above.
840 + $request_post = wp_unslash( $_POST );
841 +
645 842 // Options to update will be stored here
646 843 $update_options = array();
647 844
648 845 // Loop options and get values to save
@@ -660,9 +857,9 @@
660 857
661 858 // Standard types
662 859 case "checkbox" :
663 860
664 - if ( isset( $_POST[ $value['id'] ] ) ) {
861 + if ( isset( $request_post[ $value['id'] ] ) ) {
665 862 $option_value = 'yes';
666 863 } else {
667 864 $option_value = 'no';
668 865 }
@@ -671,10 +868,10 @@
671 868
672 869 case "textarea" :
673 870 case "wysiwyg" :
674 871
675 - if ( isset( $_POST[$value['id']] ) ) {
676 - $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'] ] ) );
677 874 } else {
678 875 $option_value = '';
679 876 }
680 877
@@ -689,10 +886,10 @@
689 886 case "single_select_page" :
690 887 case "single_select_country" :
691 888 case 'radio' :
692 889
693 - if ( isset( $_POST[$value['id']] ) ) {
694 - $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'] ] );
695 892 } else {
696 893 $option_value = '';
697 894 }
698 895
@@ -701,13 +898,17 @@
701 898 // Special types
702 899 case "multiselect" :
703 900 case "multi_select_countries" :
704 901
705 - // Get countries array
706 - if ( isset( $_POST[ $value['id'] ] ) )
707 - $selected_countries = array_map( 'ph_clean', array_map( 'stripslashes', (array) $_POST[ $value['id'] ] ) );
708 - else
709 - $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 + }
710 911
711 912 $option_value = $selected_countries;
712 913
713 914 break;
@@ -713,14 +914,15 @@
713 914 break;
714 915
715 916 case "image_width" :
716 917
717 - 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'] ) ) {
718 920
719 - $update_options[ $value['id'] ]['width'] = ph_clean( stripslashes( $_POST[ $value['id'] ]['width'] ) );
720 - $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'];
721 923
722 - if ( isset( $_POST[ $value['id'] ]['crop'] ) )
924 + if ( isset( $image_dimensions['crop'] ) )
723 925 $update_options[ $value['id'] ]['crop'] = 1;
724 926 else
725 927 $update_options[ $value['id'] ]['crop'] = 0;
726 928
@@ -777,38 +979,7 @@
777 979 update_option( $name, $value );
778 980
779 981 return true;
780 982 }
781 -
782 - /**
783 - * Checks which method we're using to serve downloads
784 - *
785 - * If using force or x-sendfile, this ensures the .htaccess is in place
786 - *
787 - * @access public
788 - * @return void
789 - */
790 - /*public static function check_download_folder_protection() {
791 - $upload_dir = wp_upload_dir();
792 - $downloads_url = $upload_dir['basedir'] . '/propertyhive_uploads';
793 - $download_method = get_option('propertyhive_file_download_method');
794 -
795 - if ( $download_method == 'redirect' ) {
796 -
797 - // Redirect method - don't protect
798 - if ( file_exists( $downloads_url . '/.htaccess' ) )
799 - unlink( $downloads_url . '/.htaccess' );
800 -
801 - } else {
802 -
803 - // Force method - protect, add rules to the htaccess file
804 - if ( ! file_exists( $downloads_url . '/.htaccess' ) ) {
805 - if ( $file_handle = @fopen( $downloads_url . '/.htaccess', 'w' ) ) {
806 - fwrite( $file_handle, 'deny from all' );
807 - fclose( $file_handle );
808 - }
809 - }
810 - }
811 - }*/
812 983 }
813 984
814 985 endif;