PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
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 1.4.62 All 260 releases
← All changes | includes/class-ph-additional-fields.php +178 -64 2.2.32.3.0 View file →
@@ -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();