| @@ -1,10 +1,14 @@ | ||
| 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. | |
| 2 | 4 | |
| 5 | + | |
| 3 | 6 | if ( ! defined( 'ABSPATH' ) ) { |
| 4 | 7 | exit; // Exit if accessed directly |
| 5 | 8 | } |
| 6 | 9 | |
| 10 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Preserve the existing public PH_Additional_Fields extension-compatible class name. | |
| 7 | 11 | class PH_Additional_Fields { |
| 8 | 12 | |
| 9 | 13 | public function __construct() { |
| 10 | 14 | |
| @@ -50,9 +54,9 @@ | ||
| 50 | 54 | $custom_field['admin_list'] == '1' && |
| 51 | 55 | substr($custom_field['meta_box'], 0, (strlen($post_type)+1)) == $post_type .'_' |
| 52 | 56 | ) |
| 53 | 57 | { |
| 54 | - $existing_columns[$custom_field['field_name']] = __( $custom_field['field_label'], 'propertyhive' ); | |
| 58 | + $existing_columns[$custom_field['field_name']] = $custom_field['field_label']; | |
| 55 | 59 | } |
| 56 | 60 | } |
| 57 | 61 | } |
| 58 | 62 | |
| @@ -85,9 +89,9 @@ | ||
| 85 | 89 | } |
| 86 | 90 | } |
| 87 | 91 | elseif ( $custom_field['field_type'] == 'date' ) |
| 88 | 92 | { |
| 89 | - echo date(get_option( 'date_format' ), strtotime(get_post_meta( $post_id, $custom_field['field_name'], true ))); | |
| 93 | + echo esc_html( gmdate(get_option( 'date_format' ), strtotime(get_post_meta( $post_id, $custom_field['field_name'], true ))) ); | |
| 90 | 94 | } |
| 91 | 95 | elseif ( $custom_field['field_type'] == 'image' ) |
| 92 | 96 | { |
| 93 | 97 | $image_id = get_post_meta( $post_id, $custom_field['field_name'], true ); |
| @@ -181,8 +185,9 @@ | ||
| 181 | 185 | { |
| 182 | 186 | if ( $custom_field['field_name'] == $vars['orderby'] ) |
| 183 | 187 | { |
| 184 | 188 | $vars = array_merge( $vars, array( |
| 189 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Only a configured sortable custom field for this admin post type reaches this metadata ordering; preserve its existing text-sort contract. | |
| 185 | 190 | 'meta_key' => $custom_field['field_name'], |
| 186 | 191 | 'orderby' => 'meta_value' |
| 187 | 192 | ) ); |
| 188 | 193 | } |
| @@ -217,9 +222,9 @@ | ||
| 217 | 222 | add_filter( 'propertyhive_room_breakdown_data', array( $this, 'add_custom_fields_to_room_breakdown' ), 10, 3 ); // Applicable when Rooms / Student Accommodation add on active |
| 218 | 223 | |
| 219 | 224 | $meta_boxes_done = array(); |
| 220 | 225 | $office_details_fields_exist = false; |
| 221 | - $offices_opening_section_done = false; | |
| 226 | + $propertyhive_offices_opening_section_done = false; | |
| 222 | 227 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 223 | 228 | { |
| 224 | 229 | if ( !in_array( $custom_field['meta_box'], $meta_boxes_done ) ) |
| 225 | 230 | { |
| @@ -224,12 +229,13 @@ | ||
| 224 | 229 | if ( !in_array( $custom_field['meta_box'], $meta_boxes_done ) ) |
| 225 | 230 | { |
| 226 | 231 | if ( substr( $custom_field['meta_box'], 0, 6 ) == 'office' ) |
| 227 | 232 | { |
| 228 | - add_filter( 'propertyhive_' . $custom_field['meta_box'] . '_settings', function( $settings ) | |
| 233 | + add_filter( 'propertyhive_' . $custom_field['meta_box'] . '_settings', function( $settings ) use ( &$propertyhive_offices_opening_section_done ) | |
| 229 | 234 | { |
| 230 | - global $offices_opening_section_done; | |
| 235 | + | |
| 231 | 236 | |
| 237 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The settings filter reads an optional id to populate office setting defaults and returns a settings array. It does not save options or posts; settings writes occur in a separate guarded save callback. | |
| 232 | 238 | $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id']; |
| 233 | 239 | |
| 234 | 240 | $meta_box_being_done = str_replace( "propertyhive_", "", current_filter() ); |
| 235 | 241 | $meta_box_being_done = str_replace( "_settings", "", $meta_box_being_done ); |
| @@ -239,12 +245,12 @@ | ||
| 239 | 245 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 240 | 246 | { |
| 241 | 247 | if ( $custom_field['meta_box'] == $meta_box_being_done ) |
| 242 | 248 | { |
| 243 | - if ( !$offices_opening_section_done ) | |
| 249 | + if ( !$propertyhive_offices_opening_section_done ) | |
| 244 | 250 | { |
| 245 | 251 | $settings[] = array( 'title' => __( 'Additional Fields', 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'office_template_assistant_additional_field' ); |
| 246 | - $offices_opening_section_done = true; | |
| 252 | + $propertyhive_offices_opening_section_done = true; | |
| 247 | 253 | } |
| 248 | 254 | |
| 249 | 255 | switch ( $custom_field['field_type'] ) |
| 250 | 256 | { |
| @@ -296,9 +302,9 @@ | ||
| 296 | 302 | } |
| 297 | 303 | } |
| 298 | 304 | } |
| 299 | 305 | |
| 300 | - if ( $offices_opening_section_done ) | |
| 306 | + if ( $propertyhive_offices_opening_section_done ) | |
| 301 | 307 | { |
| 302 | 308 | $settings[] = array( 'type' => 'sectionend', 'id' => 'office_template_assistant_additional_field'); |
| 303 | 309 | } |
| 304 | 310 | |
| @@ -320,9 +326,12 @@ | ||
| 320 | 326 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 321 | 327 | { |
| 322 | 328 | if ( $custom_field['meta_box'] == $meta_box_being_done ) |
| 323 | 329 | { |
| 324 | - update_post_meta( $post_id, $custom_field['field_name'], (isset($_POST[$custom_field['field_name']]) ? $_POST[$custom_field['field_name']] : '') ); | |
| 330 | + $field_value = $this->get_submitted_custom_field_value( $custom_field, true ); | |
| 331 | + if ( null !== $field_value ) { | |
| 332 | + update_post_meta( $post_id, $custom_field['field_name'], wp_slash( $field_value ) ); | |
| 333 | + } | |
| 325 | 334 | } |
| 326 | 335 | } |
| 327 | 336 | } |
| 328 | 337 | }); |
| @@ -361,10 +370,10 @@ | ||
| 361 | 370 | } |
| 362 | 371 | elseif ( isset($custom_field['field_type']) && $custom_field['field_type'] == 'multiselect' ) |
| 363 | 372 | { |
| 364 | 373 | ?> |
| 365 | - <p class="form-field <?php echo esc_attr($custom_field['field_name']); ?>_field"><label for="<?php echo esc_attr($custom_field['field_name']); ?>"><?php echo esc_html(__( $custom_field['field_label'], 'propertyhive' )); ?></label> | |
| 366 | - <select id="<?php echo esc_attr($custom_field['field_name']); ?>" name="<?php echo esc_attr($custom_field['field_name']); ?>[]" multiple="multiple" data-placeholder="<?php echo esc_attr(__( 'Select ' . $custom_field['field_label'], 'propertyhive' )); ?>" class="multiselect attribute_values"> | |
| 374 | + <p class="form-field <?php echo esc_attr($custom_field['field_name']); ?>_field"><label for="<?php echo esc_attr($custom_field['field_name']); ?>"><?php echo esc_html($custom_field['field_label']); ?></label> | |
| 375 | + <select id="<?php echo esc_attr($custom_field['field_name']); ?>" name="<?php echo esc_attr($custom_field['field_name']); ?>[]" multiple="multiple" data-placeholder="<?php echo esc_attr(/* translators: %s: Field label. */ sprintf( __( 'Select %s', 'propertyhive' ), $custom_field['field_label'] )); ?>" class="multiselect attribute_values"> | |
| 367 | 376 | <?php |
| 368 | 377 | $selected_values = get_post_meta( $thepostid, $custom_field['field_name'], true ); |
| 369 | 378 | if ( !is_array($selected_values) && $selected_values == '' ) |
| 370 | 379 | { |
| @@ -462,16 +471,12 @@ | ||
| 462 | 471 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 463 | 472 | { |
| 464 | 473 | if ( $custom_field['meta_box'] == $meta_box_being_done ) |
| 465 | 474 | { |
| 466 | - if ( isset($custom_field['field_type']) && $custom_field['field_type'] == 'textarea' ) | |
| 467 | - { | |
| 468 | - update_post_meta( $post_id, $custom_field['field_name'], (isset($_POST[$custom_field['field_name']]) ? sanitize_textarea_field($_POST[$custom_field['field_name']]) : '') ); | |
| 469 | - } | |
| 470 | - else | |
| 471 | - { | |
| 472 | - update_post_meta( $post_id, $custom_field['field_name'], (isset($_POST[$custom_field['field_name']]) ? ph_clean($_POST[$custom_field['field_name']]) : '') ); | |
| 473 | - } | |
| 475 | + $field_value = $this->get_submitted_custom_field_value( $custom_field, true ); | |
| 476 | + if ( null !== $field_value ) { | |
| 477 | + update_post_meta( $post_id, $custom_field['field_name'], wp_slash( $field_value ) ); | |
| 478 | + } | |
| 474 | 479 | } |
| 475 | 480 | } |
| 476 | 481 | } |
| 477 | 482 | }); |
| @@ -482,9 +487,9 @@ | ||
| 482 | 487 | } |
| 483 | 488 | |
| 484 | 489 | if ( $office_details_fields_exist ) |
| 485 | 490 | { |
| 486 | - add_filter( 'propertyhive_' . $custom_field['meta_box'] . '_settings', function( $settings ) | |
| 491 | + add_filter( 'propertyhive_' . $custom_field['meta_box'] . '_settings', function( $settings ) use ( &$propertyhive_offices_opening_section_done ) | |
| 487 | 492 | { |
| 488 | 493 | $settings[] = array( 'type' => 'sectionend', 'id' => 'office_location_options' ); |
| 489 | 494 | |
| 490 | 495 | return $settings; |
| @@ -534,8 +539,9 @@ | ||
| 534 | 539 | ) |
| 535 | 540 | { |
| 536 | 541 | if ( !isset($args['meta_query']) ) |
| 537 | 542 | { |
| 543 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Configured shortcode fields are stored in post metadata; append their existing scalar/multiselect predicates without replacing other query constraints. | |
| 538 | 544 | $args['meta_query'] = array(); |
| 539 | 545 | } |
| 540 | 546 | |
| 541 | 547 | // Format meta query as "= value" or "LIKE value" |
| @@ -593,9 +599,9 @@ | ||
| 593 | 599 | $values = get_post_meta( $property->id, $custom_field['field_name'], TRUE ); |
| 594 | 600 | |
| 595 | 601 | if ( !empty($values) ) |
| 596 | 602 | { |
| 597 | - echo '<li class="' . esc_attr(trim($custom_field['field_name'], '_')) . '">' . $label; | |
| 603 | + echo '<li class="' . esc_attr(trim($custom_field['field_name'], '_')) . '">' . wp_kses_post( $label ); | |
| 598 | 604 | echo esc_html(is_array($values) ? implode(", ", $values) : $values); |
| 599 | 605 | echo '</li>'; |
| 600 | 606 | } |
| 601 | 607 | } |
| @@ -604,9 +610,9 @@ | ||
| 604 | 610 | if ( $property->{$custom_field['field_name']} != '' ) |
| 605 | 611 | { |
| 606 | 612 | ?> |
| 607 | 613 | <li class="<?php echo esc_attr(trim($custom_field['field_name'], '_')); ?>"> |
| 608 | - <?php echo $label . date(get_option( 'date_format' ), strtotime($property->{$custom_field['field_name']})); ?> | |
| 614 | + <?php echo wp_kses_post( $label ) . esc_html( gmdate(get_option( 'date_format' ), strtotime($property->{$custom_field['field_name']})) ); ?> | |
| 609 | 615 | </li> |
| 610 | 616 | <?php |
| 611 | 617 | } |
| 612 | 618 | } |
| @@ -615,9 +621,9 @@ | ||
| 615 | 621 | if ( $property->{$custom_field['field_name']} != '' ) |
| 616 | 622 | { |
| 617 | 623 | ?> |
| 618 | 624 | <li class="<?php echo esc_attr(trim($custom_field['field_name'], '_')); ?>"> |
| 619 | - <?php echo $label . wp_get_attachment_image($property->{$custom_field['field_name']}); ?> | |
| 625 | + <?php echo wp_kses_post( $label . wp_get_attachment_image($property->{$custom_field['field_name']}) ); ?> | |
| 620 | 626 | </li> |
| 621 | 627 | <?php |
| 622 | 628 | } |
| 623 | 629 | } |
| @@ -626,9 +632,9 @@ | ||
| 626 | 632 | if ( $property->{$custom_field['field_name']} != '' ) |
| 627 | 633 | { |
| 628 | 634 | ?> |
| 629 | 635 | <li class="<?php echo esc_attr(trim($custom_field['field_name'], '_')); ?>"> |
| 630 | - <?php echo $label . '<a href="' . esc_url(wp_get_attachment_url($property->{$custom_field['field_name']})) . '" rel="noopener noreferrer" target="_blank">' . esc_html(__( 'View', 'propertyhive' )) . '</a>'; ?> | |
| 636 | + <?php echo wp_kses_post( $label ) . '<a href="' . esc_url(wp_get_attachment_url($property->{$custom_field['field_name']})) . '" rel="noopener noreferrer" target="_blank">' . esc_html(__( 'View', 'propertyhive' )) . '</a>'; ?> | |
| 631 | 637 | </li> |
| 632 | 638 | <?php |
| 633 | 639 | } |
| 634 | 640 | } |
| @@ -651,9 +657,9 @@ | ||
| 651 | 657 | } |
| 652 | 658 | } |
| 653 | 659 | ?> |
| 654 | 660 | <li class="<?php echo esc_attr(trim($custom_field['field_name'], '_')); ?>"> |
| 655 | - <?php echo $label . $value; ?> | |
| 661 | + <?php echo wp_kses_post( $label . $value ); ?> | |
| 656 | 662 | </li> |
| 657 | 663 | <?php |
| 658 | 664 | } |
| 659 | 665 | } |
| @@ -712,8 +718,44 @@ | ||
| 712 | 718 | } |
| 713 | 719 | return $form_controls; |
| 714 | 720 | } |
| 715 | 721 | |
| 722 | + /** | |
| 723 | + * Read a configured custom field without changing the shared request. | |
| 724 | + * | |
| 725 | + * A null result indicates a malformed value and leaves existing metadata intact. | |
| 726 | + * Callers run through the office, meta-box or user-registration save gates. | |
| 727 | + */ | |
| 728 | + private function get_submitted_custom_field_value( $custom_field, $multiline = false, $default = '' ) | |
| 729 | + { | |
| 730 | + $field_name = $custom_field['field_name']; | |
| 731 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Internal input helper used by the nonce-checked office, meta-box and user-details save callbacks; authorization belongs to those distinct entry points. | |
| 732 | + if ( ! isset( $_POST[$field_name] ) ) { | |
| 733 | + return $default; | |
| 734 | + } | |
| 735 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Input helper; the calling save entry point verifies its own nonce. | |
| 736 | + if ( is_string( $_POST[$field_name] ) ) { | |
| 737 | + if ( $multiline && isset( $custom_field['field_type'] ) && 'textarea' === $custom_field['field_type'] ) { | |
| 738 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Input helper; the calling save entry point verifies its own nonce. | |
| 739 | + return sanitize_textarea_field( wp_unslash( $_POST[$field_name] ) ); | |
| 740 | + } | |
| 741 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Input helper; the calling save entry point verifies its own nonce. | |
| 742 | + return sanitize_text_field( wp_unslash( $_POST[$field_name] ) ); | |
| 743 | + } | |
| 744 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Input helper; the calling save entry point verifies its own nonce. | |
| 745 | + if ( isset( $custom_field['field_type'] ) && 'multiselect' === $custom_field['field_type'] && is_array( $_POST[$field_name] ) ) { | |
| 746 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- This loop validates element types only; every accepted element is sanitized and unslashed in the return below. | |
| 747 | + foreach ( $_POST[$field_name] as $field_value ) { | |
| 748 | + if ( ! is_string( $field_value ) ) { | |
| 749 | + return null; | |
| 750 | + } | |
| 751 | + } | |
| 752 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Input helper; the calling save entry point verifies its own nonce. | |
| 753 | + return ph_clean( wp_unslash( $_POST[$field_name] ) ); | |
| 754 | + } | |
| 755 | + return null; | |
| 756 | + } | |
| 757 | + | |
| 716 | 758 | public function save_custom_fields_on_user_details( $contact_post_id, $user_id ) |
| 717 | 759 | { |
| 718 | 760 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 719 | 761 | |
| @@ -722,15 +764,38 @@ | ||
| 722 | 764 | foreach ( $custom_fields as $custom_field ) |
| 723 | 765 | { |
| 724 | 766 | if ( isset($custom_field['display_on_user_details']) && $custom_field['display_on_user_details'] == '1' && substr($custom_field['meta_box'], 0, 8) == 'contact_' ) |
| 725 | 767 | { |
| 726 | - update_post_meta( $contact_post_id, $custom_field['field_name'], (isset($_POST[$custom_field['field_name']]) ? ph_clean($_POST[$custom_field['field_name']]) : '') ); | |
| 768 | + $field_value = $this->get_submitted_custom_field_value( $custom_field ); | |
| 769 | + if ( null !== $field_value ) { | |
| 770 | + update_post_meta( $contact_post_id, $custom_field['field_name'], wp_slash( $field_value ) ); | |
| 771 | + } | |
| 727 | 772 | } |
| 728 | 773 | } |
| 729 | 774 | } |
| 730 | 775 | |
| 776 | + /** Validate the scalar or flat selection-list shape used by search controls. */ | |
| 777 | + private function is_valid_custom_field_filter( $value, $field_type ) | |
| 778 | + { | |
| 779 | + if ( is_string( $value ) ) { | |
| 780 | + return true; | |
| 781 | + } | |
| 782 | + if ( ! in_array( $field_type, array( 'select', 'multiselect' ), true ) || ! is_array( $value ) ) { | |
| 783 | + return false; | |
| 784 | + } | |
| 785 | + foreach ( $value as $selection ) { | |
| 786 | + if ( ! is_string( $selection ) ) { | |
| 787 | + return false; | |
| 788 | + } | |
| 789 | + } | |
| 790 | + return true; | |
| 791 | + } | |
| 792 | + | |
| 731 | 793 | public function custom_fields_in_meta_query( $meta_query ) |
| 732 | 794 | { |
| 795 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only property/applicant search filter; no data is saved or sent. | |
| 796 | + $filter_department = ( isset( $_REQUEST['department'] ) && is_string( $_REQUEST['department'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['department'] ) ) : null; | |
| 797 | + | |
| 733 | 798 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 734 | 799 | |
| 735 | 800 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| 736 | 801 | { |
| @@ -735,8 +800,13 @@ | ||
| 735 | 800 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| 736 | 801 | { |
| 737 | 802 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 738 | 803 | { |
| 804 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only property/applicant search filter; no data is saved or sent. | |
| 805 | + $filter_value = isset( $_REQUEST[$custom_field['field_name']] ) ? ph_clean( wp_unslash( $_REQUEST[$custom_field['field_name']] ) ) : null; | |
| 806 | + if ( null !== $filter_value && ! $this->is_valid_custom_field_filter( $filter_value, $custom_field['field_type'] ) ) { | |
| 807 | + continue; | |
| 808 | + } | |
| 739 | 809 | if ( |
| 740 | 810 | $custom_field['meta_box'] == 'property_residential_sales_details' |
| 741 | 811 | || |
| 742 | 812 | $custom_field['meta_box'] == 'property_residential_lettings_details' |
| @@ -749,10 +819,12 @@ | ||
| 749 | 819 | $meta_box_department = str_replace("_details", "", $meta_box_department); |
| 750 | 820 | $meta_box_department = str_replace("_", "-", $meta_box_department); |
| 751 | 821 | |
| 752 | 822 | if ( |
| 753 | - isset( $_REQUEST['department'] ) && | |
| 754 | - ( $_REQUEST['department'] == $meta_box_department || ph_get_custom_department_based_on($_REQUEST['department']) == $meta_box_department ) | |
| 823 | + | |
| 824 | + null !== $filter_department && | |
| 825 | + | |
| 826 | + ( $filter_department == $meta_box_department || ph_get_custom_department_based_on($filter_department) == $meta_box_department ) | |
| 755 | 827 | ) |
| 756 | 828 | { |
| 757 | 829 | |
| 758 | 830 | } |
| @@ -766,13 +838,15 @@ | ||
| 766 | 838 | { |
| 767 | 839 | if ( $custom_field['exact_match'] == '' ) |
| 768 | 840 | { |
| 769 | 841 | // not exact match (i.e. pets allowed) |
| 770 | - if ( isset($_REQUEST[$custom_field['field_name']]) && ph_clean( $_REQUEST[$custom_field['field_name']] ) == 'yes' ) | |
| 842 | + | |
| 843 | + if ( null !== $filter_value && ph_clean( $filter_value ) == 'yes' ) | |
| 771 | 844 | { |
| 772 | 845 | $meta_query[] = array( |
| 773 | 846 | 'key' => $custom_field['field_name'], |
| 774 | - 'value' => ph_clean( $_REQUEST[$custom_field['field_name']] ), | |
| 847 | + | |
| 848 | + 'value' => ph_clean( $filter_value ), | |
| 775 | 849 | ); |
| 776 | 850 | } |
| 777 | 851 | } |
| 778 | 852 | else |
| @@ -777,9 +851,10 @@ | ||
| 777 | 851 | } |
| 778 | 852 | else |
| 779 | 853 | { |
| 780 | 854 | // should match exactly only (i.e. something only) |
| 781 | - if ( isset($_REQUEST[$custom_field['field_name']]) && ph_clean( $_REQUEST[$custom_field['field_name']] ) == 'yes' ) | |
| 855 | + | |
| 856 | + if ( null !== $filter_value && ph_clean( $filter_value ) == 'yes' ) | |
| 782 | 857 | { |
| 783 | 858 | $meta_query[] = array( |
| 784 | 859 | 'key' => $custom_field['field_name'], |
| 785 | 860 | 'value' => 'yes', |
| @@ -804,18 +879,21 @@ | ||
| 804 | 879 | } |
| 805 | 880 | else |
| 806 | 881 | { |
| 807 | 882 | if ( |
| 808 | - isset( $_REQUEST[$custom_field['field_name']] ) && $_REQUEST[$custom_field['field_name']] != '' | |
| 883 | + | |
| 884 | + null !== $filter_value && $filter_value != '' | |
| 809 | 885 | ) |
| 810 | 886 | { |
| 811 | 887 | if ( |
| 812 | 888 | ( $custom_field['field_type'] == 'select' || $custom_field['field_type'] == 'multiselect' ) && |
| 813 | - is_array($_REQUEST[$custom_field['field_name']]) | |
| 889 | + | |
| 890 | + is_array($filter_value) | |
| 814 | 891 | ) |
| 815 | 892 | { |
| 816 | 893 | $sub_meta_query = array('relation' => 'OR'); |
| 817 | - foreach ( $_REQUEST[$custom_field['field_name']] as $value ) | |
| 894 | + | |
| 895 | + foreach ( $filter_value as $value ) | |
| 818 | 896 | { |
| 819 | 897 | $sub_meta_query[] = array( |
| 820 | 898 | 'key' => $custom_field['field_name'], |
| 821 | 899 | 'value' => ph_clean( $value ), |
| @@ -832,9 +910,10 @@ | ||
| 832 | 910 | elseif ( $custom_field['field_type'] == 'select' ) |
| 833 | 911 | { |
| 834 | 912 | $meta_query[] = array( |
| 835 | 913 | 'key' => $custom_field['field_name'], |
| 836 | - 'value' => ph_clean( $_REQUEST[$custom_field['field_name']] ), | |
| 914 | + | |
| 915 | + 'value' => ph_clean( $filter_value ), | |
| 837 | 916 | 'compare' => '=', |
| 838 | 917 | ); |
| 839 | 918 | } |
| 840 | 919 | else |
| @@ -840,9 +919,10 @@ | ||
| 840 | 919 | else |
| 841 | 920 | { |
| 842 | 921 | $meta_query[] = array( |
| 843 | 922 | 'key' => $custom_field['field_name'], |
| 844 | - 'value' => ph_clean( $_REQUEST[$custom_field['field_name']] ), | |
| 923 | + | |
| 924 | + 'value' => ph_clean( $filter_value ), | |
| 845 | 925 | 'compare' => 'LIKE', |
| 846 | 926 | ); |
| 847 | 927 | } |
| 848 | 928 | } |
| @@ -970,10 +1050,10 @@ | ||
| 970 | 1050 | $options[$dropdown_option] = ph_clean($dropdown_option); |
| 971 | 1051 | } |
| 972 | 1052 | ?> |
| 973 | 1053 | <p class="form-field"> |
| 974 | - <label for="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo $applicant_profile_id; ?>"><?php echo esc_html($custom_field['field_label']); ?></label> | |
| 975 | - <select id="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo $applicant_profile_id; ?>" name="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo $applicant_profile_id; ?>[]" multiple="multiple" data-placeholder="Start typing to add <?php echo esc_attr($custom_field['field_label']); ?>..." class="multiselect attribute_values"> | |
| 1054 | + <label for="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo esc_attr( $applicant_profile_id ); ?>"><?php echo esc_html($custom_field['field_label']); ?></label> | |
| 1055 | + <select id="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo esc_attr( $applicant_profile_id ); ?>" name="_applicant<?php echo esc_attr($custom_field['field_name']); ?>_<?php echo esc_attr( $applicant_profile_id ); ?>[]" multiple="multiple" data-placeholder="Start typing to add <?php echo esc_attr($custom_field['field_label']); ?>..." class="multiselect attribute_values"> | |
| 976 | 1056 | <?php |
| 977 | 1057 | foreach ( $options as $option ) |
| 978 | 1058 | { |
| 979 | 1059 | echo '<option value="' . esc_attr( $option ) . '"'; |
| @@ -1107,8 +1187,11 @@ | ||
| 1107 | 1187 | |
| 1108 | 1188 | public function save_applicant_requirements_fields( $contact_post_id, $applicant_profile_id ) |
| 1109 | 1189 | { |
| 1110 | 1190 | $applicant_profile = get_post_meta( $contact_post_id, '_applicant_profile_' . $applicant_profile_id, TRUE ); |
| 1191 | + if ( ! is_array( $applicant_profile ) ) { | |
| 1192 | + $applicant_profile = array(); | |
| 1193 | + } | |
| 1111 | 1194 | |
| 1112 | 1195 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1113 | 1196 | |
| 1114 | 1197 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| @@ -1116,24 +1199,32 @@ | ||
| 1116 | 1199 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 1117 | 1200 | { |
| 1118 | 1201 | if ( isset($custom_field['display_on_applicant_requirements']) && $custom_field['display_on_applicant_requirements'] == '1' && substr($custom_field['meta_box'], 0, 9) == 'property_' ) |
| 1119 | 1202 | { |
| 1203 | + $submitted_field = $custom_field; | |
| 1204 | + $submitted_field['field_name'] = '_applicant' . $custom_field['field_name'] . '_' . $applicant_profile_id; | |
| 1205 | + $field_value = $this->get_submitted_custom_field_value( $submitted_field ); | |
| 1206 | + if ( null === $field_value ) { | |
| 1207 | + continue; | |
| 1208 | + } | |
| 1120 | 1209 | switch ( $custom_field['field_type'] ) |
| 1121 | 1210 | { |
| 1122 | 1211 | case "select": |
| 1123 | 1212 | case "multiselect": |
| 1124 | 1213 | { |
| 1214 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Presence check only; the contact save entry point verifies the nonce before this hook and values are normalized by get_submitted_custom_field_value. | |
| 1125 | 1215 | if ( isset($_POST['_applicant' . $custom_field['field_name'] . '_' . $applicant_profile_id]) ) |
| 1126 | 1216 | { |
| 1127 | - $applicant_profile[$custom_field['field_name']] = ph_clean($_POST['_applicant' . $custom_field['field_name'] . '_' . $applicant_profile_id]); | |
| 1217 | + $applicant_profile[$custom_field['field_name']] = $field_value; | |
| 1128 | 1218 | } |
| 1129 | 1219 | break; |
| 1130 | 1220 | } |
| 1131 | 1221 | case "checkbox": |
| 1132 | 1222 | { |
| 1223 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Presence check only; the contact save entry point verifies the nonce before this hook and values are normalized by get_submitted_custom_field_value. | |
| 1133 | 1224 | if ( isset($_POST['_applicant' . $custom_field['field_name'] . '_' . $applicant_profile_id]) ) |
| 1134 | 1225 | { |
| 1135 | - $applicant_profile[$custom_field['field_name']] = ph_clean($_POST['_applicant' . $custom_field['field_name'] . '_' . $applicant_profile_id]); | |
| 1226 | + $applicant_profile[$custom_field['field_name']] = $field_value; | |
| 1136 | 1227 | } |
| 1137 | 1228 | else |
| 1138 | 1229 | { |
| 1139 | 1230 | $applicant_profile[$custom_field['field_name']] = ''; |
| @@ -1144,9 +1235,9 @@ | ||
| 1144 | 1235 | } |
| 1145 | 1236 | } |
| 1146 | 1237 | } |
| 1147 | 1238 | |
| 1148 | - update_post_meta( $contact_post_id, '_applicant_profile_' . $applicant_profile_id, $applicant_profile ); | |
| 1239 | + update_post_meta( $contact_post_id, '_applicant_profile_' . $applicant_profile_id, wp_slash( $applicant_profile ) ); | |
| 1149 | 1240 | } |
| 1150 | 1241 | |
| 1151 | 1242 | public function applicant_requirements_display( $requirements, $contact_post_id, $applicant_profile ) |
| 1152 | 1243 | { |
| @@ -1527,9 +1618,18 @@ | ||
| 1527 | 1618 | } |
| 1528 | 1619 | |
| 1529 | 1620 | public function applicant_registered( $contact_post_id, $user_id ) |
| 1530 | 1621 | { |
| 1531 | - $applicant_profile = get_post_meta( $contact_post_id, '_applicant_profile_' . ( isset($_POST['profile_id']) && $_POST['profile_id'] != '' ? (int)$_POST['profile_id'] : '0' ), TRUE ); | |
| 1622 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Registration/account callback runs after its parent AJAX nonce and ownership checks; this hook only updates the supplied contact. | |
| 1623 | + if ( isset( $_POST['profile_id'] ) && ! is_string( $_POST['profile_id'] ) ) { | |
| 1624 | + return; | |
| 1625 | + } | |
| 1626 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The parent registration/account AJAX callback verifies its nonce before dispatching this hook. | |
| 1627 | + $profile_id = isset( $_POST['profile_id'] ) ? absint( $_POST['profile_id'] ) : 0; | |
| 1628 | + $applicant_profile = get_post_meta( $contact_post_id, '_applicant_profile_' . $profile_id, TRUE ); | |
| 1629 | + if ( ! is_array( $applicant_profile ) ) { | |
| 1630 | + $applicant_profile = array(); | |
| 1631 | + } | |
| 1532 | 1632 | |
| 1533 | 1633 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1534 | 1634 | |
| 1535 | 1635 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| @@ -1537,30 +1637,27 @@ | ||
| 1537 | 1637 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 1538 | 1638 | { |
| 1539 | 1639 | if ( isset($custom_field['display_on_applicant_requirements']) && $custom_field['display_on_applicant_requirements'] == '1' && substr($custom_field['meta_box'], 0, 9) == 'property_' ) |
| 1540 | 1640 | { |
| 1641 | + $field_value = $this->get_submitted_custom_field_value( $custom_field, false, 'multiselect' === $custom_field['field_type'] ? array() : '' ); | |
| 1642 | + if ( null === $field_value ) { | |
| 1643 | + continue; | |
| 1644 | + } | |
| 1541 | 1645 | switch ( $custom_field['field_type'] ) |
| 1542 | 1646 | { |
| 1543 | 1647 | case "select": |
| 1544 | 1648 | { |
| 1545 | - $applicant_profile[$custom_field['field_name']] = isset($_POST[$custom_field['field_name']]) ? ph_clean($_POST[$custom_field['field_name']]) : ''; | |
| 1649 | + $applicant_profile[$custom_field['field_name']] = $field_value; | |
| 1546 | 1650 | break; |
| 1547 | 1651 | } |
| 1548 | 1652 | case "multiselect": |
| 1549 | 1653 | { |
| 1550 | - if ( isset($_POST[$custom_field['field_name']]) ) | |
| 1551 | - { | |
| 1552 | - if ( !is_array($_POST[$custom_field['field_name']]) ) | |
| 1553 | - { | |
| 1554 | - $_POST[$custom_field['field_name']] = array($_POST[$custom_field['field_name']]); | |
| 1555 | - } | |
| 1556 | - } | |
| 1557 | - $applicant_profile[$custom_field['field_name']] = isset($_POST[$custom_field['field_name']]) ? ph_clean($_POST[$custom_field['field_name']]) : array(); | |
| 1654 | + $applicant_profile[$custom_field['field_name']] = is_array( $field_value ) ? $field_value : array( $field_value ); | |
| 1558 | 1655 | break; |
| 1559 | 1656 | } |
| 1560 | 1657 | case "checkbox": |
| 1561 | 1658 | { |
| 1562 | - $applicant_profile[$custom_field['field_name']] = isset($_POST[$custom_field['field_name']]) ? ph_clean($_POST[$custom_field['field_name']]) : ''; | |
| 1659 | + $applicant_profile[$custom_field['field_name']] = $field_value; | |
| 1563 | 1660 | break; |
| 1564 | 1661 | } |
| 1565 | 1662 | } |
| 1566 | 1663 | } |
| @@ -1566,13 +1663,16 @@ | ||
| 1566 | 1663 | } |
| 1567 | 1664 | } |
| 1568 | 1665 | } |
| 1569 | 1666 | |
| 1570 | - update_post_meta( $contact_post_id, '_applicant_profile_' . ( isset($_POST['profile_id']) && $_POST['profile_id'] != '' ? (int)$_POST['profile_id'] : '0' ), $applicant_profile ); | |
| 1667 | + update_post_meta( $contact_post_id, '_applicant_profile_' . $profile_id, wp_slash( $applicant_profile ) ); | |
| 1571 | 1668 | } |
| 1572 | 1669 | |
| 1573 | 1670 | public function applicant_list_check( $check, $contact_post_id, $applicant_profile ) |
| 1574 | 1671 | { |
| 1672 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only property/applicant search filter; no data is saved or sent. | |
| 1673 | + $filter_department = ( isset( $_POST['department'] ) && is_string( $_POST['department'] ) ) ? sanitize_text_field( wp_unslash( $_POST['department'] ) ) : null; | |
| 1674 | + | |
| 1575 | 1675 | $current_settings = get_option( 'propertyhive_template_assistant', array() ); |
| 1576 | 1676 | |
| 1577 | 1677 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| 1578 | 1678 | { |
| @@ -1577,8 +1677,13 @@ | ||
| 1577 | 1677 | if ( isset($current_settings['custom_fields']) && !empty($current_settings['custom_fields']) ) |
| 1578 | 1678 | { |
| 1579 | 1679 | foreach ( $current_settings['custom_fields'] as $custom_field ) |
| 1580 | 1680 | { |
| 1681 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only property/applicant search filter; no data is saved or sent. | |
| 1682 | + $filter_value = isset( $_POST[$custom_field['field_name']] ) ? ph_clean( wp_unslash( $_POST[$custom_field['field_name']] ) ) : null; | |
| 1683 | + if ( null !== $filter_value && ! $this->is_valid_custom_field_filter( $filter_value, $custom_field['field_type'] ) ) { | |
| 1684 | + return false; | |
| 1685 | + } | |
| 1581 | 1686 | if ( isset($custom_field['display_on_applicant_requirements']) && $custom_field['display_on_applicant_requirements'] == '1' && substr($custom_field['meta_box'], 0, 9) == 'property_' ) |
| 1582 | 1687 | { |
| 1583 | 1688 | // ensure if field is specific to department it's taken into account, else ignored |
| 1584 | 1689 | if ( |
| @@ -1593,10 +1698,12 @@ | ||
| 1593 | 1698 | $meta_box_department = str_replace("_details", "", $meta_box_department); |
| 1594 | 1699 | $meta_box_department = str_replace("_", "-", $meta_box_department); |
| 1595 | 1700 | |
| 1596 | 1701 | if ( |
| 1597 | - isset( $_POST['department'] ) && | |
| 1598 | - ( $_POST['department'] == $meta_box_department || ph_get_custom_department_based_on($_POST['department']) == $meta_box_department ) | |
| 1702 | + | |
| 1703 | + null !== $filter_department && | |
| 1704 | + | |
| 1705 | + ( $filter_department == $meta_box_department || ph_get_custom_department_based_on($filter_department) == $meta_box_department ) | |
| 1599 | 1706 | ) |
| 1600 | 1707 | { |
| 1601 | 1708 | |
| 1602 | 1709 | } |
| @@ -1612,13 +1719,15 @@ | ||
| 1612 | 1719 | switch ( $custom_field['field_type'] ) |
| 1613 | 1720 | { |
| 1614 | 1721 | case "select": |
| 1615 | 1722 | { |
| 1616 | - if ( !empty($_POST[$custom_field['field_name']]) ) | |
| 1723 | + | |
| 1724 | + if ( !empty($filter_value) ) | |
| 1617 | 1725 | { |
| 1618 | 1726 | if ( |
| 1619 | 1727 | $applicant_profile[$custom_field['field_name']] == '' || |
| 1620 | - $_POST[$custom_field['field_name']] == $applicant_profile[$custom_field['field_name']] | |
| 1728 | + | |
| 1729 | + $filter_value == $applicant_profile[$custom_field['field_name']] | |
| 1621 | 1730 | ) |
| 1622 | 1731 | { |
| 1623 | 1732 | |
| 1624 | 1733 | } |
| @@ -1630,9 +1739,10 @@ | ||
| 1630 | 1739 | break; |
| 1631 | 1740 | } |
| 1632 | 1741 | case "multiselect": |
| 1633 | 1742 | { |
| 1634 | - if ( !empty($_POST[$custom_field['field_name']]) ) | |
| 1743 | + | |
| 1744 | + if ( !empty($filter_value) ) | |
| 1635 | 1745 | { |
| 1636 | 1746 | if ( !is_array($applicant_profile[$custom_field['field_name']]) && $applicant_profile[$custom_field['field_name']] != '' ) |
| 1637 | 1747 | { |
| 1638 | 1748 | $applicant_profile[$custom_field['field_name']] = array($applicant_profile[$custom_field['field_name']]); |
| @@ -1643,9 +1753,10 @@ | ||
| 1643 | 1753 | |
| 1644 | 1754 | } |
| 1645 | 1755 | else |
| 1646 | 1756 | { |
| 1647 | - $property_values = $_POST[$custom_field['field_name']]; | |
| 1757 | + | |
| 1758 | + $property_values = is_array( $filter_value ) ? $filter_value : array( $filter_value ); | |
| 1648 | 1759 | if ( empty($property_values) ) |
| 1649 | 1760 | { |
| 1650 | 1761 | return false; |
| 1651 | 1762 | } |
| @@ -1680,9 +1791,10 @@ | ||
| 1680 | 1791 | { |
| 1681 | 1792 | // not exact match (i.e. pets allowed) |
| 1682 | 1793 | if ( |
| 1683 | 1794 | $applicant_profile[$custom_field['field_name']] == '' || |
| 1684 | - $_POST[$custom_field['field_name']] == $applicant_profile[$custom_field['field_name']] | |
| 1795 | + | |
| 1796 | + $filter_value == $applicant_profile[$custom_field['field_name']] | |
| 1685 | 1797 | ) |
| 1686 | 1798 | { |
| 1687 | 1799 | |
| 1688 | 1800 | } |
| @@ -1693,12 +1805,14 @@ | ||
| 1693 | 1805 | } |
| 1694 | 1806 | else |
| 1695 | 1807 | { |
| 1696 | 1808 | // exact match |
| 1697 | - if ( isset($_POST[$custom_field['field_name']]) ) | |
| 1809 | + | |
| 1810 | + if ( null !== $filter_value ) | |
| 1698 | 1811 | { |
| 1699 | 1812 | if ( |
| 1700 | - $_POST[$custom_field['field_name']] == $applicant_profile[$custom_field['field_name']] | |
| 1813 | + | |
| 1814 | + $filter_value == $applicant_profile[$custom_field['field_name']] | |
| 1701 | 1815 | ) |
| 1702 | 1816 | { |
| 1703 | 1817 | |
| 1704 | 1818 | } |
| @@ -1745,9 +1859,9 @@ | ||
| 1745 | 1859 | if ( $room->{$custom_field['field_name']} != '' ) |
| 1746 | 1860 | { |
| 1747 | 1861 | $room_data[] = array( |
| 1748 | 1862 | 'class' => sanitize_title($custom_field['field_name']), |
| 1749 | - 'label' => __( $custom_field['field_label'], 'propertyhive' ), | |
| 1863 | + 'label' => $custom_field['field_label'], | |
| 1750 | 1864 | 'value' => $room->{$custom_field['field_name']} |
| 1751 | 1865 | ); |
| 1752 | 1866 | } |
| 1753 | 1867 | } |
| @@ -1757,5 +1871,5 @@ | ||
| 1757 | 1871 | return $room_data; |
| 1758 | 1872 | } |
| 1759 | 1873 | } |
| 1760 | 1874 | |
| 1761 | -new PH_Additional_Fields(); | |
| 1875 | +new PH_Additional_Fields(); | |