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/admin/class-ph-admin-post-types.php +285 -134 2.2.62.3.0 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean
3 +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate.
4 +
2 5 /**
3 6 * Post Types Admin
4 7 *
5 8 * @author PropertyHive
@@ -14,8 +17,9 @@
14 17
15 18 /**
16 19 * PH_Admin_Post_Types Class
17 20 */
21 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Post_Types; preserving the existing PH_* class name is required for plugin and extension compatibility.
18 22 class PH_Admin_Post_Types {
19 23
20 24 /**
21 25 * Constructor
@@ -53,8 +57,32 @@
53 57
54 58 add_filter( 'post_row_actions', array( $this, 'modify_post_row_actions_for_archived' ), 10, 2 );
55 59 }
56 60
61 + /**
62 + * Read one scalar admin query value after WordPress unslashes and sanitizes it.
63 + *
64 + * Admin list filters are read-only, but their values still flow into markup and
65 + * query arguments. Returning an empty value for arrays keeps scalar filters
66 + * from accidentally accepting a malformed request while preserving the
67 + * existing empty-filter behaviour.
68 + *
69 + * @param string $key Query-string key.
70 + * @return string
71 + */
72 + private function get_admin_query_value( $key ) {
73 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
74 + if ( ! isset( $_GET[ $key ] ) || ! is_scalar( $_GET[ $key ] ) ) {
75 + return '';
76 + }
77 +
78 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only admin list value is copied, unslashed immediately below, and sanitized before use; the sniffer reports the source assignment instead of the sanitization boundary.
79 + $raw_value = $_GET[ $key ];
80 + $raw_value = wp_unslash( (string) $raw_value );
81 +
82 + return sanitize_text_field( $raw_value );
83 + }
84 +
57 85 public function handle_bulk_action_archive_and_unarchive($redirect_to, $doaction, $post_ids)
58 86 {
59 87 if ($doaction === 'move_to_archive')
60 88 {
@@ -202,9 +230,9 @@
202 230
203 231 $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
204 232 $post_type = get_post_type($post_id);
205 233
206 - if ( !wp_verify_nonce($_GET['_wpnonce'], 'archive-post_' . $post_id) )
234 + if ( !wp_verify_nonce( ( isset( $_GET['_wpnonce'] ) && is_string( $_GET['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '', 'archive-post_' . $post_id) )
207 235 {
208 236 wp_die(esc_html(__('Security check failed.', 'propertyhive')));
209 237 }
210 238
@@ -226,9 +254,9 @@
226 254 wp_die(esc_html(__('An error occurred while archiving the post.', 'propertyhive')));
227 255 }
228 256
229 257 // Redirect to the main list of contacts
230 - wp_redirect(admin_url('edit.php?post_type=' . $post_type));
258 + wp_safe_redirect(admin_url('edit.php?post_type=' . $post_type));
231 259 exit;
232 260 }
233 261
234 262 public function handle_unarchive_action()
@@ -239,9 +267,9 @@
239 267
240 268 $post_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
241 269 $post_type = get_post_type($post_id);
242 270
243 - if ( !wp_verify_nonce($_GET['_wpnonce'], 'unarchive-post_' . $post_id) )
271 + if ( !wp_verify_nonce( ( isset( $_GET['_wpnonce'] ) && is_string( $_GET['_wpnonce'] ) ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '', 'unarchive-post_' . $post_id) )
244 272 {
245 273 wp_die(esc_html(__('Security check failed.', 'propertyhive')));
246 274 }
247 275
@@ -265,13 +293,13 @@
265 293
266 294 // Redirect to the main list of contacts
267 295 if ( isset($_GET['return']) && $_GET['return'] === 'archive' )
268 296 {
269 - wp_redirect(admin_url('edit.php?post_status=archive&post_type=' . get_post_type($post_id)));
297 + wp_safe_redirect(admin_url('edit.php?post_status=archive&post_type=' . get_post_type($post_id)));
270 298 }
271 299 else
272 300 {
273 - wp_redirect(admin_url('edit.php?post_type=' . get_post_type($post_id)));
301 + wp_safe_redirect(admin_url('edit.php?post_type=' . get_post_type($post_id)));
274 302 }
275 303 exit;
276 304 }
277 305
@@ -456,8 +484,9 @@
456 484 $output .= $this->property_location_filter();
457 485 $output .= $this->property_office_filter();
458 486 $output .= $this->negotiator_filter();
459 487
488 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
460 489 echo apply_filters( 'propertyhive_property_filters', $output );
461 490 }
462 491
463 492 /**
@@ -467,14 +496,16 @@
467 496 global $wp_query;
468 497
469 498 $departments = ph_get_departments();
470 499
471 - $selected_department = isset( $_GET['_department'] ) && in_array( $_GET['_department'], array_keys($departments) ) ? $_GET['_department'] : '';
500 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
501 + $requested_value = isset( $_GET['_department'] ) && is_string( $_GET['_department'] ) ? sanitize_text_field( wp_unslash( $_GET['_department'] ) ) : '';
502 + $selected_department = array_key_exists( $requested_value, $departments ) ? $requested_value : '';
472 503
473 504 // Department filtering
474 505 $output = '<select name="_department" id="dropdown_property_department">';
475 506
476 - $output .= '<option value="">' . __( 'All Departments', 'propertyhive' ) . '</option>';
507 + $output .= '<option value="">' . esc_html__( 'All Departments', 'propertyhive' ) . '</option>';
477 508
478 509 foreach ( $departments as $key => $value )
479 510 {
480 511 if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
@@ -498,9 +529,9 @@
498 529
499 530 // Department filtering
500 531 $output = '<select name="_office_id" id="dropdown_property_office_id">';
501 532
502 - $output .= '<option value="">' . __( 'All Offices', 'propertyhive' ) . '</option>';
533 + $output .= '<option value="">' . esc_html__( 'All Offices', 'propertyhive' ) . '</option>';
503 534
504 535 $args = array(
505 536 'post_type' => 'office',
506 537 'nopaging' => true,
@@ -515,10 +546,12 @@
515 546 {
516 547 $office_query->the_post();
517 548
518 549 $output .= '<option value="' . esc_attr($post->ID) . '"';
550 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
519 551 if ( isset( $_GET['_office_id'] ) && ! empty( $_GET['_office_id'] ) )
520 552 {
553 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
521 554 $output .= selected( $post->ID, (int)$_GET['_office_id'], false );
522 555 }
523 556 $output .= '>' . esc_html(get_the_title()) . '</option>';
524 557 }
@@ -538,11 +571,13 @@
538 571
539 572 return wp_dropdown_users(array(
540 573 'name' => '_negotiator_id',
541 574 'id' => 'dropdown_property_negotiator_id',
542 - 'show_option_all' => __( 'All Negotiators', 'propertyhive' ),
575 + 'show_option_all' => esc_html__( 'All Negotiators', 'propertyhive' ),
576 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
543 577 'selected' => empty( $_GET['_negotiator_id'] ) ? '' : (int)$_GET['_negotiator_id'],
544 578 'echo' => false,
579 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Legacy Property Negotiator compatibility filter; existing role filters depend on this exact public hook name.
545 580 'role__not_in' => apply_filters( 'property_negotiator_exclude_roles', array('property_hive_contact', 'subscriber') )
546 581 ));
547 582 }
548 583
@@ -550,9 +585,10 @@
550 585 * Show a date range selector
551 586 */
552 587 public function date_range_filter() {
553 588
554 - $date_range_label = empty( $_GET['_date_range_label'] ) ? __( 'Any Time', 'propertyhive' ) : $_GET['_date_range_label'];
589 + $date_range_label = $this->get_admin_query_value( '_date_range_label' );
590 + $date_range_label = empty( $date_range_label ) ? __( 'Any Time', 'propertyhive' ) : $date_range_label;
555 591
556 592 // The date picker doesn't have a concept of 'Any Time', so valid dates must be used
557 593 // I've used the last and first date of the month (reversed) as it's a range that is not selectable, but is within the current month
558 594 // If I used an already labelled date range (e.g. 'Today'), it would show as 'Today' when selected
@@ -557,10 +593,12 @@
557 593 // I've used the last and first date of the month (reversed) as it's a range that is not selectable, but is within the current month
558 594 // If I used an already labelled date range (e.g. 'Today'), it would show as 'Today' when selected
559 595 // If I use a nearby date range (e.g. 'Yesterday'), if someone actually selected that range it would show as 'Any Time'
560 596 // If I use a unlikely date range (e.g. 01-01-1970 - 31-12-2070), the custom date range picker would open showing Jan 1970.
561 - $date_range_from = empty( $_GET['_date_range_from'] ) ? date('Y-m-d', strtotime('last day of this month')) : $_GET['_date_range_from'];
562 - $date_range_to = empty( $_GET['_date_range_to'] ) ? date('Y-m-d', strtotime('first day of this month')) : $_GET['_date_range_to'];
597 + $date_range_from = $this->get_admin_query_value( '_date_range_from' );
598 + $date_range_from = empty( $date_range_from ) ? gmdate('Y-m-d', strtotime('last day of this month')) : $date_range_from;
599 + $date_range_to = $this->get_admin_query_value( '_date_range_to' );
600 + $date_range_to = empty( $date_range_to ) ? gmdate('Y-m-d', strtotime('first day of this month')) : $date_range_to;
563 601
564 602 return "
565 603 <select name='_date_range_label' id='date_range' style='max-width:25rem;'>
566 604 <option selected>" . esc_html($date_range_label) . "</option>
@@ -583,9 +621,9 @@
583 621 $args = array(
584 622 'hide_empty' => false,
585 623 'parent' => 0
586 624 );
587 - $terms = get_terms( 'location', $args );
625 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
588 626
589 627 if ( !empty( $terms ) && !is_wp_error( $terms ) )
590 628 {
591 629 foreach ($terms as $term)
@@ -595,9 +633,9 @@
595 633 $args = array(
596 634 'hide_empty' => false,
597 635 'parent' => $term->term_id
598 636 );
599 - $subterms = get_terms( 'location', $args );
637 + $subterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
600 638
601 639 if ( !empty( $subterms ) && !is_wp_error( $subterms ) )
602 640 {
603 641 foreach ($subterms as $term)
@@ -607,9 +645,9 @@
607 645 $args = array(
608 646 'hide_empty' => false,
609 647 'parent' => $term->term_id
610 648 );
611 - $subsubterms = get_terms( 'location', $args );
649 + $subsubterms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'location' ) ) );
612 650
613 651 if ( !empty( $subsubterms ) && !is_wp_error( $subsubterms ) )
614 652 {
615 653 foreach ($subsubterms as $term)
@@ -628,10 +666,12 @@
628 666 {
629 667 foreach ( $options as $value => $label )
630 668 {
631 669 $output .= '<option value="' . esc_attr($value) . '"';
670 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
632 671 if ( isset( $_GET['_location_id'] ) && ! empty( $_GET['_location_id'] ) )
633 672 {
673 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
634 674 $output .= selected( $value, (int)$_GET['_location_id'], false );
635 675 }
636 676 $output .= '>' . esc_html($label) . '</option>';
637 677 }
@@ -655,9 +695,9 @@
655 695 $args = array(
656 696 'hide_empty' => false,
657 697 'parent' => 0
658 698 );
659 - $terms = get_terms( 'availability', $args );
699 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'availability' ) ) );
660 700
661 701 if ( !empty( $terms ) && !is_wp_error( $terms ) )
662 702 {
663 703 foreach ($terms as $term)
@@ -672,10 +712,12 @@
672 712 {
673 713 foreach ( $options as $value => $label )
674 714 {
675 715 $output .= '<option value="' . esc_attr($value) . '"';
716 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
676 717 if ( isset( $_GET['_availability_id'] ) && ! empty( $_GET['_availability_id'] ) )
677 718 {
719 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
678 720 $output .= selected( $value, (int)$_GET['_availability_id'], false );
679 721 }
680 722 $output .= '>' . esc_html($label) . '</option>';
681 723 }
@@ -694,9 +736,9 @@
694 736
695 737 // Availability filtering
696 738 $output = '<select name="_marketing" id="dropdown_property_marketing">';
697 739
698 - $output .= '<option value="">' . __( 'All Marketing Statuses', 'propertyhive' ) . '</option>';
740 + $output .= '<option value="">' . esc_html__( 'All Marketing Statuses', 'propertyhive' ) . '</option>';
699 741
700 742 $options = array(
701 743 'on_market' => __( 'On Market Only', 'propertyhive' ),
702 744 'off_market' => __( 'Not On Market Only', 'propertyhive' ),
@@ -706,9 +748,9 @@
706 748 $args = array(
707 749 'hide_empty' => false,
708 750 'parent' => 0
709 751 );
710 - $terms = get_terms( 'marketing_flag', $args );
752 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'marketing_flag' ) ) );
711 753
712 754 if ( !empty( $terms ) && !is_wp_error( $terms ) )
713 755 {
714 756 foreach ($terms as $term)
@@ -717,15 +759,16 @@
717 759 }
718 760 }
719 761
720 762 $options = apply_filters( 'propertyhive_property_filter_marketing_options', $options );
763 + $selected_marketing = $this->get_admin_query_value( '_marketing' );
721 764
722 765 foreach ( $options as $key => $value )
723 766 {
724 767 $output .= '<option value="' . esc_attr($key) . '"';
725 - if ( isset( $_GET['_marketing'] ) && ! empty( $_GET['_marketing'] ) )
768 + if ( ! empty( $selected_marketing ) )
726 769 {
727 - $output .= selected( $key, sanitize_text_field($_GET['_marketing']), false );
770 + $output .= selected( $key, $selected_marketing, false );
728 771 }
729 772 $output .= '>' . esc_html($value) . '</option>';
730 773 }
731 774
@@ -739,9 +782,11 @@
739 782 */
740 783 public function contact_filters() {
741 784 global $wp_query;
742 785
743 - $selected_contact_type = isset( $_GET['_contact_type'] ) && in_array( $_GET['_contact_type'], array( 'owner', 'potentialowner', 'applicant', 'hotapplicant', 'thirdparty' ) ) ? ph_clean($_GET['_contact_type']) : '';
786 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
787 + $requested_value = isset( $_GET['_contact_type'] ) && is_string( $_GET['_contact_type'] ) ? sanitize_text_field( wp_unslash( $_GET['_contact_type'] ) ) : '';
788 + $selected_contact_type = in_array( $requested_value, array( 'owner', 'potentialowner', 'applicant', 'hotapplicant', 'thirdparty' ), true ) ? $requested_value : '';
744 789
745 790 // Type filtering
746 791 $options = array();
747 792
@@ -795,8 +840,9 @@
795 840 }
796 841
797 842 $output .= $this->date_range_filter('Date Created');
798 843
844 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
799 845 echo apply_filters( 'propertyhive_contact_filters', $output );
800 846 }
801 847
802 848 /**
@@ -813,8 +859,9 @@
813 859 $output .= $this->enquiry_source_filter();
814 860 $output .= $this->enquiry_office_filter();
815 861 $output .= $this->enquiry_negotiator_filter();
816 862
863 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
817 864 echo apply_filters( 'propertyhive_enquiry_filters', $output );
818 865 }
819 866
820 867 /**
@@ -822,9 +869,11 @@
822 869 */
823 870 public function enquiry_status_filter() {
824 871 global $wp_query;
825 872
826 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'all', 'open', 'closed' ) ) ? $_GET['_status'] : '';
873 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
874 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
875 + $selected_status = in_array( $requested_value, array( 'all', 'open', 'closed' ), true ) ? $requested_value : '';
827 876
828 877 // Status filtering
829 878 $output = '<select name="_status" id="dropdown_enquiry_status">
830 879 <option value="all"' . selected( 'all', $selected_status, false ) . '>All</option>';
@@ -833,8 +882,9 @@
833 882
834 883 foreach ( $enquiry_statuses as $status => $display_status )
835 884 {
836 885 $output .= '<option value="' . esc_attr($status) . '"';
886 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
837 887 if ( $status == $selected_status || ( $status == 'open' && ( !isset($_GET['_status']) || empty($_GET['_status']) ) ) )
838 888 {
839 889 $output .= ' selected';
840 890 }
@@ -863,19 +913,20 @@
863 913 asort($sources);
864 914
865 915 // Status filtering
866 916 $output = '<select name="_source" id="dropdown_enquiry_source">';
917 + $selected_source = $this->get_admin_query_value( '_source' );
867 918
868 - $output .= '<option value="">' . __( 'Show all sources', 'propertyhive' ) . '</option>';
919 + $output .= '<option value="">' . esc_html__( 'Show all sources', 'propertyhive' ) . '</option>';
869 920
870 921 foreach ( $sources as $key => $value )
871 922 {
872 923 $output .= '<option value="' . esc_attr($key) . '"';
873 - if ( isset( $_GET['_source'] ) && ! empty( $_GET['_source'] ) )
924 + if ( ! empty( $selected_source ) )
874 925 {
875 - $output .= selected( $key, sanitize_text_field($_GET['_source']), false );
926 + $output .= selected( $key, $selected_source, false );
876 927 }
877 - $output .= '>' . esc_html(__( $value, 'propertyhive' )) . '</option>';
928 + $output .= '>' . esc_html( $value ) . '</option>';
878 929 }
879 930
880 931 $output .= '</select>';
881 932
@@ -890,9 +941,9 @@
890 941
891 942 // Department filtering
892 943 $output = '<select name="_office_id" id="dropdown_enquiry_office_id">';
893 944
894 - $output .= '<option value="">' . __( 'All Offices', 'propertyhive' ) . '</option>';
945 + $output .= '<option value="">' . esc_html__( 'All Offices', 'propertyhive' ) . '</option>';
895 946
896 947 $args = array(
897 948 'post_type' => 'office',
898 949 'nopaging' => true,
@@ -907,10 +958,12 @@
907 958 {
908 959 $office_query->the_post();
909 960
910 961 $output .= '<option value="' . esc_attr($post->ID) . '"';
962 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
911 963 if ( isset( $_GET['_office_id'] ) && ! empty( $_GET['_office_id'] ) )
912 964 {
965 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
913 966 $output .= selected( $post->ID, (int)$_GET['_office_id'], false );
914 967 }
915 968 $output .= '>' . esc_html(get_the_title()) . '</option>';
916 969 }
@@ -929,11 +982,13 @@
929 982 public function enquiry_negotiator_filter() {
930 983 return wp_dropdown_users(array(
931 984 'name' => '_negotiator_id',
932 985 'id' => 'dropdown_enquiry_negotiator_id',
933 - 'show_option_all' => __( 'All Negotiators', 'propertyhive' ),
986 + 'show_option_all' => esc_html__( 'All Negotiators', 'propertyhive' ),
987 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
934 988 'selected' => empty( $_GET['_negotiator_id'] ) ? '' : (int)$_GET['_negotiator_id'],
935 989 'echo' => false,
990 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Legacy Property Negotiator compatibility filter; existing role filters depend on this exact public hook name.
936 991 'role__not_in' => apply_filters( 'property_negotiator_exclude_roles', array('property_hive_contact', 'subscriber') )
937 992 ));
938 993 }
939 994
@@ -948,8 +1003,9 @@
948 1003 $output .= $this->appraisal_status_filter();
949 1004 $output .= $this->negotiator_filter();
950 1005 $output .= $this->date_range_filter();
951 1006
1007 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
952 1008 echo apply_filters( 'propertyhive_appraisal_filters', $output );
953 1009 }
954 1010
955 1011 /**
@@ -957,14 +1013,16 @@
957 1013 */
958 1014 public function appraisal_status_filter() {
959 1015 global $wp_query;
960 1016
961 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'pending', 'carried_out', 'won', 'lost', 'instructed', 'cancelled' ) ) ? ph_clean($_GET['_status']) : '';
1017 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1018 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1019 + $selected_status = in_array( $requested_value, array( 'pending', 'carried_out', 'won', 'lost', 'instructed', 'cancelled' ), true ) ? $requested_value : '';
962 1020
963 1021 // Status filtering
964 1022 $output = '<select name="_status" id="dropdown_appraisal_status">';
965 1023
966 - $output .= '<option value="">' . __( 'All Statuses', 'propertyhive' ) . '</option>';
1024 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
967 1025
968 1026 $output .= '<option value="pending"';
969 1027 $output .= selected( 'pending', $selected_status, false );
970 1028 $output .= '>' . esc_html(__( 'Pending', 'propertyhive' )) . '</option>';
@@ -1007,8 +1065,9 @@
1007 1065 $output .= $this->property_office_filter();
1008 1066 $output .= $this->negotiator_filter();
1009 1067 $output .= $this->date_range_filter();
1010 1068
1069 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1011 1070 echo apply_filters( 'propertyhive_viewing_filters', $output );
1012 1071 }
1013 1072
1014 1073 /**
@@ -1016,14 +1075,16 @@
1016 1075 */
1017 1076 public function viewing_status_filter() {
1018 1077 global $wp_query;
1019 1078
1020 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'pending', 'confirmed', 'unconfirmed', 'carried_out', 'awaiting_feedback', 'feedback_passed_on', 'feedback_not_passed_on', 'cancelled', 'no_show' ) ) ? ph_clean($_GET['_status']) : '';
1079 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1080 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1081 + $selected_status = in_array( $requested_value, array( 'pending', 'confirmed', 'unconfirmed', 'carried_out', 'awaiting_feedback', 'feedback_passed_on', 'feedback_not_passed_on', 'cancelled', 'no_show' ), true ) ? $requested_value : '';
1021 1082
1022 1083 // Status filtering
1023 1084 $output = '<select name="_status" id="dropdown_viewing_status">';
1024 1085
1025 - $output .= '<option value="">' . __( 'All Statuses', 'propertyhive' ) . '</option>';
1086 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
1026 1087
1027 1088 $viewing_statuses = ph_get_viewing_statuses();
1028 1089
1029 1090 foreach ( $viewing_statuses as $status => $display_status )
@@ -1041,8 +1102,9 @@
1041 1102
1042 1103 public function refresh_property_office_filtering( $query ) {
1043 1104 remove_filter('posts_join', array( $this, 'filter_by_property_office') );
1044 1105
1106 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1045 1107 if ( ! empty( $_GET['_office_id'] ) && in_array( $query->query['post_type'], array(
1046 1108 'viewing',
1047 1109 'offer',
1048 1110 'sale',
@@ -1054,12 +1116,15 @@
1054 1116
1055 1117 public function filter_by_property_office($query) {
1056 1118 global $wpdb;
1057 1119
1120 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only office filtering; no state change.
1121 + $office_id = isset( $_GET['_office_id'] ) && is_scalar( $_GET['_office_id'] ) ? absint( $_GET['_office_id'] ) : 0;
1122 +
1058 1123 return $query . '
1059 1124 INNER JOIN ' . $wpdb->postmeta . ' AS property_meta ON property_meta.post_id = ' . $wpdb->posts . '.ID AND property_meta.meta_key = "_property_id"
1060 1125 INNER JOIN ' . $wpdb->postmeta . ' AS property_office_meta ON property_office_meta.post_id = property_meta.meta_value AND property_office_meta.meta_key = "_office_id"
1061 - AND property_office_meta.meta_value = ' . (int)$_GET['_office_id'];
1126 + AND property_office_meta.meta_value = ' . $office_id;
1062 1127 }
1063 1128
1064 1129 /**
1065 1130 * Show an offer filter box
@@ -1072,8 +1137,9 @@
1072 1137 $output .= $this->offer_status_filter();
1073 1138 $output .= $this->property_office_filter();
1074 1139 $output .= $this->date_range_filter();
1075 1140
1141 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1076 1142 echo apply_filters( 'propertyhive_offer_filters', $output );
1077 1143 }
1078 1144
1079 1145 /**
@@ -1081,9 +1147,11 @@
1081 1147 */
1082 1148 public function offer_status_filter() {
1083 1149 global $wp_query;
1084 1150
1085 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'pending', 'accepted', 'declined' ) ) ? ph_clean($_GET['_status']) : '';
1151 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1152 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1153 + $selected_status = in_array( $requested_value, array( 'pending', 'accepted', 'declined' ), true ) ? $requested_value : '';
1086 1154
1087 1155 // Status filtering
1088 1156 $output = '<select name="_status" id="dropdown_offer_status">';
1089 1157
@@ -1114,8 +1182,9 @@
1114 1182 $output .= $this->sale_status_filter();
1115 1183 $output .= $this->property_office_filter();
1116 1184 $output .= $this->date_range_filter();
1117 1185
1186 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1118 1187 echo apply_filters( 'propertyhive_sale_filters', $output );
1119 1188 }
1120 1189
1121 1190 /**
@@ -1123,14 +1192,16 @@
1123 1192 */
1124 1193 public function sale_status_filter() {
1125 1194 global $wp_query;
1126 1195
1127 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'current', 'exchanged', 'completed', 'fallen_through' ) ) ? ph_clean($_GET['_status']) : '';
1196 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1197 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1198 + $selected_status = in_array( $requested_value, array( 'current', 'exchanged', 'completed', 'fallen_through' ), true ) ? $requested_value : '';
1128 1199
1129 1200 // Status filtering
1130 1201 $output = '<select name="_status" id="dropdown_sale_status">';
1131 1202
1132 - $output .= '<option value="">' . __( 'All Statuses', 'propertyhive' ) . '</option>';
1203 + $output .= '<option value="">' . esc_html__( 'All Statuses', 'propertyhive' ) . '</option>';
1133 1204
1134 1205 $sale_statuses = ph_get_sale_statuses();
1135 1206
1136 1207 foreach ( $sale_statuses as $status => $display_status )
@@ -1155,8 +1226,9 @@
1155 1226
1156 1227 $output .= $this->tenancy_status_filter();
1157 1228 $output .= $this->tenancy_management_type_filter();
1158 1229
1230 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1159 1231 echo apply_filters( 'propertyhive_tenancy_filters', $output );
1160 1232 }
1161 1233
1162 1234 /**
@@ -1164,9 +1236,11 @@
1164 1236 */
1165 1237 public function tenancy_status_filter() {
1166 1238 global $wp_query;
1167 1239
1168 - $selected_status = isset( $_GET['_status'] ) && in_array( $_GET['_status'], array( 'pending', 'current', 'finished') ) ? ph_clean($_GET['_status']) : '';
1240 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1241 + $requested_value = isset( $_GET['_status'] ) && is_string( $_GET['_status'] ) ? sanitize_text_field( wp_unslash( $_GET['_status'] ) ) : '';
1242 + $selected_status = in_array( $requested_value, array( 'pending', 'current', 'finished'), true ) ? $requested_value : '';
1169 1243
1170 1244 // Status filtering
1171 1245 $output = '<select name="_status" id="dropdown_tenancy_status">';
1172 1246
@@ -1199,9 +1273,11 @@
1199 1273 'let_only' => 'Let Only',
1200 1274 'fully_managed' => 'Fully Managed'
1201 1275 ) );
1202 1276
1203 - $selected_management_type = isset( $_GET['_management_type'] ) && in_array( $_GET['_management_type'], array_keys($management_types) ) ? ph_clean($_GET['_management_type']) : '';
1277 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1278 + $requested_value = isset( $_GET['_management_type'] ) && is_string( $_GET['_management_type'] ) ? sanitize_text_field( wp_unslash( $_GET['_management_type'] ) ) : '';
1279 + $selected_management_type = array_key_exists( $requested_value, $management_types ) ? $requested_value : '';
1204 1280
1205 1281 // Status filtering
1206 1282 $output = '<select name="_management_type" id="dropdown_tenancy_management_type">';
1207 1283
@@ -1210,9 +1286,9 @@
1210 1286 foreach ( $management_types as $key => $value )
1211 1287 {
1212 1288 $output .= '<option value="' . esc_attr($key) . '"';
1213 1289 $output .= selected( $key, $selected_management_type, false );
1214 - $output .= '>' . esc_html(__( $value, 'propertyhive' )) . '</option>';
1290 + $output .= '>' . esc_html( $value ) . '</option>';
1215 1291 }
1216 1292
1217 1293 $output .= '</select>';
1218 1294
@@ -1227,18 +1303,20 @@
1227 1303 $output .= $this->key_date_type_filter();
1228 1304 $output .= $this->key_date_status_filter();
1229 1305 $output .= $this->date_range_filter();
1230 1306
1307 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built-in controls escape their text and attributes before this trusted PHP filter adds complete HTML controls.
1231 1308 echo apply_filters( 'propertyhive_tenancy_filters', $output );
1232 1309 }
1233 1310
1234 1311 public function key_date_type_filter() {
1235 1312
1313 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1236 1314 $selected_value = ! empty($_GET['_key_date_type_id']) ? (int)$_GET['_key_date_type_id'] : '';
1237 - $terms = get_terms( 'management_key_date_type', array(
1315 + $terms = get_terms( array_merge( wp_parse_args( array(
1238 1316 'hide_empty' => false,
1239 1317 'parent' => 0
1240 - ) );
1318 + ) ), array( 'taxonomy' => 'management_key_date_type' ) ) );
1241 1319
1242 1320 $output = '<select name="_key_date_type_id">';
1243 1321 $output .= '<option value="">' . esc_html(__( 'All Types', 'propertyhive' )) . '</option>';
1244 1322
@@ -1259,9 +1337,11 @@
1259 1337
1260 1338
1261 1339 public function key_date_status_filter() {
1262 1340
1263 - $selected_status = isset( $_GET['status'] ) && in_array( $_GET['status'], array( 'upcoming_and_overdue', 'overdue', 'booked', 'complete', 'pending', 'on_hold', 'cancelled') ) ? ph_clean($_GET['status']) : '';
1341 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1342 + $requested_value = isset( $_GET['status'] ) && is_string( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '';
1343 + $selected_status = in_array( $requested_value, array( 'upcoming_and_overdue', 'overdue', 'booked', 'complete', 'pending', 'on_hold', 'cancelled'), true ) ? $requested_value : '';
1264 1344
1265 1345 $output = '<select name="status" id="dropdown_key_date_status">';
1266 1346
1267 1347 $output .= '<option value="">' . esc_html(__( 'All Statuses', 'propertyhive' )) . '</option>';
@@ -1306,50 +1386,71 @@
1306 1386 */
1307 1387 public function request_query( $vars ) {
1308 1388 global $typenow, $wp_query;
1309 1389
1390 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- These hooks add status/department/taxonomy/date filters to the main admin list query. WordPress supplies the list query’s pagination; values are sanitized or selected from fixed post-type/date keys. These are request_query/filter_by_date_range values consumed by the core list table query rather than independent nopaging loops. The date meta key is chosen by post type.
1310 1391 if ( !isset($vars['meta_query']) ) { $vars['meta_query'] = array(); }
1392 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- These hooks add status/department/taxonomy/date filters to the main admin list query. WordPress supplies the list query’s pagination; values are sanitized or selected from fixed post-type/date keys. These are request_query/filter_by_date_range values consumed by the core list table query rather than independent nopaging loops. The date meta key is chosen by post type.
1311 1393 if ( !isset($vars['tax_query']) ) { $vars['tax_query'] = array(); }
1312 1394
1395 + $department = $this->get_admin_query_value( '_department' );
1396 + $marketing = $this->get_admin_query_value( '_marketing' );
1397 + $contact_type = $this->get_admin_query_value( '_contact_type' );
1398 + $status = $this->get_admin_query_value( '_status' );
1399 + $source = $this->get_admin_query_value( '_source' );
1400 + $management_type = $this->get_admin_query_value( '_management_type' );
1401 + $key_date_status = $this->get_admin_query_value( 'status' );
1402 +
1313 1403 if ( 'property' === $typenow )
1314 1404 {
1315 - if ( ! empty( $_GET['_department'] ) ) {
1405 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1406 + if ( ! empty( $department ) ) {
1316 1407 $vars['meta_query'][] = array(
1317 1408 'key' => '_department',
1318 - 'value' => sanitize_text_field( $_GET['_department'] ),
1409 + 'value' => $department,
1319 1410 );
1320 1411 }
1412 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1321 1413 if ( ! empty( $_GET['_office_id'] ) ) {
1322 1414 $vars['meta_query'][] = array(
1323 1415 'key' => '_office_id',
1416 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1324 1417 'value' => (int)$_GET['_office_id'],
1325 1418 );
1326 1419 }
1420 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1327 1421 if ( ! empty( $_GET['_negotiator_id'] ) ) {
1328 1422 $vars['meta_query'][] = array(
1329 1423 'key' => '_negotiator_id',
1424 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1330 1425 'value' => (int)$_GET['_negotiator_id'],
1331 1426 );
1332 1427 }
1428 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1333 1429 if ( ! empty( $_GET['_location_id'] ) ) {
1334 1430 $vars['tax_query'][] = array(
1335 1431 'taxonomy' => 'location',
1432 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1336 1433 'terms' => ( (is_array($_GET['_location_id'])) ? (int)$_GET['_location_id'] : array( (int)$_GET['_location_id'] ) )
1337 1434 );
1338 1435 }
1436 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1339 1437 if ( ! empty( $_GET['_availability_id'] ) ) {
1340 1438 $vars['tax_query'][] = array(
1341 1439 'taxonomy' => 'availability',
1440 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1342 1441 'terms' => ( (is_array($_GET['_availability_id'])) ? (int)$_GET['_availability_id'] : array( (int)$_GET['_availability_id'] ) )
1343 1442 );
1344 1443 }
1345 - if ( ! empty( $_GET['_marketing'] ) && $_GET['_marketing'] == 'on_market' ) {
1444 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1445 + if ( 'on_market' === $marketing ) {
1346 1446 $vars['meta_query'][] = array(
1347 1447 'key' => '_on_market',
1348 1448 'value' => 'yes',
1349 1449 );
1350 1450 }
1351 - if ( ! empty( $_GET['_marketing'] ) && $_GET['_marketing'] == 'off_market' ) {
1451 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1452 + if ( 'off_market' === $marketing ) {
1352 1453 $vars['meta_query'][] = array(
1353 1454 'key' => '_on_market',
1354 1455 'value' => 'yes',
1355 1456 'compare' => '!=',
@@ -1354,16 +1455,18 @@
1354 1455 'value' => 'yes',
1355 1456 'compare' => '!=',
1356 1457 );
1357 1458 }
1358 - if ( ! empty( $_GET['_marketing'] ) && $_GET['_marketing'] == 'featured' ) {
1459 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1460 + if ( 'featured' === $marketing ) {
1359 1461 $vars['meta_query'][] = array(
1360 1462 'key' => '_featured',
1361 1463 'value' => 'yes',
1362 1464 );
1363 - }
1364 - if ( ! empty( $_GET['_marketing'] ) && substr($_GET['_marketing'], 0, 15) == 'marketing_flag_' ) {
1365 - $marketing_flag_id = sanitize_text_field( str_replace("marketing_flag_", "", $_GET['_marketing']) );
1465 + }
1466 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1467 + if ( 0 === strpos( $marketing, 'marketing_flag_' ) ) {
1468 + $marketing_flag_id = str_replace( 'marketing_flag_', '', $marketing );
1366 1469 $vars['tax_query'][] = array(
1367 1470 'taxonomy' => 'marketing_flag',
1368 1471 'terms' => ( (is_array($marketing_flag_id)) ? $marketing_flag_id : array( $marketing_flag_id ) )
1369 1472 );
@@ -1370,11 +1473,11 @@
1370 1473 }
1371 1474 }
1372 1475 elseif ( 'contact' === $typenow )
1373 1476 {
1374 - if ( ! empty( $_GET['_contact_type'] ) )
1477 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1478 + if ( ! empty( $contact_type ) )
1375 1479 {
1376 - $contact_type = ph_clean($_GET['_contact_type']);
1377 1480 if ( $contact_type == 'hotapplicant' )
1378 1481 {
1379 1482 $contact_type = 'applicant';
1380 1483
@@ -1393,18 +1496,20 @@
1393 1496 $vars = $this->filter_by_date_range($vars, 'date_query');
1394 1497 }
1395 1498 elseif ( 'enquiry' === $typenow )
1396 1499 {
1397 - if ( ! empty( $_GET['_status'] ) && ph_clean($_GET['_status']) != 'all' ) {
1500 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1501 + if ( ! empty( $status ) && $status != 'all' ) {
1398 1502
1399 1503 $vars['meta_query'][] = array(
1400 1504 'key' => '_status',
1401 - 'value' => sanitize_text_field( $_GET['_status'] ),
1505 + 'value' => $status,
1402 1506 );
1403 1507 }
1404 1508 else
1405 1509 {
1406 - if ( empty( $_GET['_status'] ) )
1510 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1511 + if ( empty( $status ) )
1407 1512 {
1408 1513 $vars['meta_query'][] = array(
1409 1514 'key' => '_status',
1410 1515 'value' => 'open',
@@ -1410,23 +1515,28 @@
1410 1515 'value' => 'open',
1411 1516 );
1412 1517 }
1413 1518 }
1414 - if ( ! empty( $_GET['_source'] ) ) {
1519 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1520 + if ( ! empty( $source ) ) {
1415 1521 $vars['meta_query'][] = array(
1416 1522 'key' => '_source',
1417 - 'value' => sanitize_text_field( $_GET['_source'] ),
1523 + 'value' => $source,
1418 1524 );
1419 1525 }
1526 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1420 1527 if ( ! empty( $_GET['_office_id'] ) ) {
1421 1528 $vars['meta_query'][] = array(
1422 1529 'key' => '_office_id',
1530 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1423 1531 'value' => (int)$_GET['_office_id'],
1424 1532 );
1425 1533 }
1534 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1426 1535 if ( ! empty( $_GET['_negotiator_id'] ) ) {
1427 1536 $vars['meta_query'][] = array(
1428 1537 'key' => '_negotiator_id',
1538 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1429 1539 'value' => (int)$_GET['_negotiator_id'],
1430 1540 );
1431 1541 }
1432 1542
@@ -1433,10 +1543,11 @@
1433 1543 $vars = $this->filter_by_date_range($vars, 'date_query');
1434 1544 }
1435 1545 elseif ( 'appraisal' === $typenow )
1436 1546 {
1437 - if ( ! empty( $_GET['_status'] ) ) {
1438 - switch ( sanitize_text_field( $_GET['_status'] ) )
1547 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1548 + if ( ! empty( $status ) ) {
1549 + switch ( $status )
1439 1550 {
1440 1551 case "confirmed":
1441 1552 {
1442 1553 $vars['meta_query'][] = array(
@@ -1464,17 +1575,19 @@
1464 1575 default:
1465 1576 {
1466 1577 $vars['meta_query'][] = array(
1467 1578 'key' => '_status',
1468 - 'value' => sanitize_text_field( $_GET['_status'] ),
1579 + 'value' => $status,
1469 1580 );
1470 1581 }
1471 1582 }
1472 1583 }
1584 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1473 1585 if ( ! empty( $_GET['_negotiator_id'] ) )
1474 1586 {
1475 1587 $vars['meta_query'][] = array(
1476 1588 'key' => '_negotiator_id',
1589 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1477 1590 'value' => (int)$_GET['_negotiator_id'],
1478 1591 );
1479 1592 }
1480 1593
@@ -1481,17 +1594,21 @@
1481 1594 $vars = $this->filter_by_date_range($vars);
1482 1595 }
1483 1596 elseif ( 'viewing' === $typenow )
1484 1597 {
1485 - if ( ! empty( $_GET['_status'] ) ) {
1598 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1599 + if ( ! empty( $status ) ) {
1486 1600
1487 - $vars['meta_query'] = add_viewing_status_meta_query( $vars['meta_query'], sanitize_text_field( $_GET['_status'] ) );
1601 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query,WordPress.Security.NonceVerification.Recommended -- Read-only status filtering of the paginated core viewing list uses the existing viewing metadata schema; no state change.
1602 + $vars['meta_query'] = add_viewing_status_meta_query( $vars['meta_query'], $status );
1488 1603
1489 1604 }
1605 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1490 1606 if ( ! empty( $_GET['_negotiator_id'] ) )
1491 1607 {
1492 1608 $vars['meta_query'][] = array(
1493 1609 'key' => '_negotiator_id',
1610 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1494 1611 'value' => (int)$_GET['_negotiator_id'],
1495 1612 );
1496 1613 }
1497 1614
@@ -1498,12 +1615,13 @@
1498 1615 $vars = $this->filter_by_date_range($vars);
1499 1616 }
1500 1617 elseif ( 'offer' === $typenow )
1501 1618 {
1502 - if ( ! empty( $_GET['_status'] ) ) {
1619 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1620 + if ( ! empty( $status ) ) {
1503 1621 $vars['meta_query'][] = array(
1504 1622 'key' => '_status',
1505 - 'value' => sanitize_text_field( $_GET['_status'] ),
1623 + 'value' => $status,
1506 1624 );
1507 1625 }
1508 1626
1509 1627 $vars = $this->filter_by_date_range($vars, '_offer_date_time');
@@ -1509,12 +1627,13 @@
1509 1627 $vars = $this->filter_by_date_range($vars, '_offer_date_time');
1510 1628 }
1511 1629 elseif ( 'sale' === $typenow )
1512 1630 {
1513 - if ( ! empty( $_GET['_status'] ) ) {
1631 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1632 + if ( ! empty( $status ) ) {
1514 1633 $vars['meta_query'][] = array(
1515 1634 'key' => '_status',
1516 - 'value' => sanitize_text_field( $_GET['_status'] ),
1635 + 'value' => $status,
1517 1636 );
1518 1637 }
1519 1638
1520 1639 $vars = $this->filter_by_date_range($vars, '_sale_date_time');
@@ -1520,16 +1639,18 @@
1520 1639 $vars = $this->filter_by_date_range($vars, '_sale_date_time');
1521 1640 }
1522 1641 elseif ( 'tenancy' === $typenow )
1523 1642 {
1524 - if ( ! empty( $_GET['_status'] ) )
1643 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1644 + if ( ! empty( $status ) )
1525 1645 {
1526 - switch ( $_GET['_status'] )
1646 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1647 + switch ( $status )
1527 1648 {
1528 1649 case 'pending' :
1529 1650 $vars['meta_query'][] = array(
1530 1651 'key' => '_start_date',
1531 - 'value' => date('Y-m-d'),
1652 + 'value' => gmdate('Y-m-d'),
1532 1653 'type' => 'date',
1533 1654 'compare' => '>',
1534 1655 );
1535 1656 break;
@@ -1539,15 +1660,15 @@
1539 1660 'relation' => 'OR',
1540 1661 array(
1541 1662 array(
1542 1663 'key' => '_start_date',
1543 - 'value' => date('Y-m-d'),
1664 + 'value' => gmdate('Y-m-d'),
1544 1665 'type' => 'date',
1545 1666 'compare' => '<=',
1546 1667 ),
1547 1668 array(
1548 1669 'key' => '_end_date',
1549 - 'value' => date('Y-m-d'),
1670 + 'value' => gmdate('Y-m-d'),
1550 1671 'type' => 'date',
1551 1672 'compare' => '>=',
1552 1673 )
1553 1674 ),
@@ -1553,9 +1674,9 @@
1553 1674 ),
1554 1675 array(
1555 1676 array(
1556 1677 'key' => '_start_date',
1557 - 'value' => date('Y-m-d'),
1678 + 'value' => gmdate('Y-m-d'),
1558 1679 'type' => 'date',
1559 1680 'compare' => '<=',
1560 1681 ),
1561 1682 array(
@@ -1569,9 +1690,9 @@
1569 1690
1570 1691 case 'finished':
1571 1692 $vars['meta_query'][] = array(
1572 1693 'key' => '_end_date',
1573 - 'value' => date('Y-m-d'),
1694 + 'value' => gmdate('Y-m-d'),
1574 1695 'type' => 'date',
1575 1696 'compare' => '<',
1576 1697 );
1577 1698 break;
@@ -1577,20 +1698,22 @@
1577 1698 break;
1578 1699 }
1579 1700 }
1580 1701
1581 - if ( ! empty( $_GET['_management_type'] ) ) {
1702 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1703 + if ( ! empty( $management_type ) ) {
1582 1704 $vars['meta_query'][] = array(
1583 1705 'key' => '_management_type',
1584 - 'value' => sanitize_text_field( $_GET['_management_type'] ),
1706 + 'value' => $management_type,
1585 1707 );
1586 1708 }
1587 1709 }
1588 1710 elseif ( 'key_date' === $typenow )
1589 1711 {
1590 - if ( ! empty( $_GET['status'] ) ) {
1712 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1713 + if ( ! empty( $key_date_status ) ) {
1591 1714
1592 - $value = sanitize_text_field( $_GET['status'] );
1715 + $value = $key_date_status;
1593 1716
1594 1717 switch ($value) {
1595 1718 case 'booked':
1596 1719 case 'complete':
@@ -1614,9 +1737,9 @@
1614 1737 'compare' => 'IN'
1615 1738 );
1616 1739 $vars['meta_query'][] = array(
1617 1740 'key' => '_date_due',
1618 - 'value' => date("Y-m-d"),
1741 + 'value' => gmdate("Y-m-d"),
1619 1742 'type' => 'date',
1620 1743 'compare' => '<',
1621 1744 );
1622 1745 break;
@@ -1636,12 +1759,14 @@
1636 1759 break;
1637 1760 }
1638 1761 }
1639 1762
1763 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1640 1764 if ( !empty( $_GET['_key_date_type_id'] ) )
1641 1765 {
1642 1766 $vars['meta_query'][] = array(
1643 1767 'key' => '_key_date_type_id',
1768 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1644 1769 'value' => (int)$_GET['_key_date_type_id'],
1645 1770 );
1646 1771 }
1647 1772
@@ -1654,36 +1779,41 @@
1654 1779 }
1655 1780
1656 1781 private function filter_by_date_range($vars, $meta_key = '_start_date_time')
1657 1782 {
1783 + $date_range_label = $this->get_admin_query_value( '_date_range_label' );
1784 + $date_range_from = $this->get_admin_query_value( '_date_range_from' );
1785 + $date_range_to = $this->get_admin_query_value( '_date_range_to' );
1786 +
1658 1787 if (
1659 - ! empty( $_GET['_date_range_label'] )
1660 - && ! empty( $_GET['_date_range_from'] )
1661 - && ! empty( $_GET['_date_range_to'] )
1662 - && $_GET['_date_range_label'] !== 'Any Time'
1663 - && DateTime::createFromFormat('Y-m-d', $_GET['_date_range_from']) !== false
1664 - && DateTime::createFromFormat('Y-m-d', $_GET['_date_range_to']) !== false
1788 + ! empty( $date_range_label )
1789 + && ! empty( $date_range_from )
1790 + && ! empty( $date_range_to )
1791 + && $date_range_label !== 'Any Time'
1792 + && DateTime::createFromFormat('Y-m-d', $date_range_from) !== false
1793 + && DateTime::createFromFormat('Y-m-d', $date_range_to) !== false
1665 1794 )
1666 1795 {
1667 1796 if ( $meta_key == 'date_query' )
1668 1797 {
1669 1798 $vars['date_query'] = array(
1670 - 'after' => $_GET['_date_range_from'] . ' 00:00:00',
1671 - 'before' => $_GET['_date_range_to'] . ' 23:59:59',
1799 + 'after' => $date_range_from . ' 00:00:00',
1800 + 'before' => $date_range_to . ' 23:59:59',
1672 1801 );
1673 1802 }
1674 1803 else
1675 1804 {
1805 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Add validated date boundaries using the fixed date key selected for this paginated admin post-type list.
1676 1806 $vars['meta_query'] = array_merge($vars['meta_query'], array (
1677 1807 array(
1678 1808 'key' => $meta_key,
1679 - 'value' => ph_clean($_GET['_date_range_from']),
1809 + 'value' => $date_range_from,
1680 1810 'type' => 'date',
1681 1811 'compare' => '>='
1682 1812 ),
1683 1813 array(
1684 1814 'key' => $meta_key,
1685 - 'value' => ph_clean($_GET['_date_range_to']),
1815 + 'value' => $date_range_to,
1686 1816 'type' => 'date',
1687 1817 'compare' => '<='
1688 1818 ),
1689 1819 ));
@@ -1698,10 +1828,13 @@
1698 1828
1699 1829 if ( !$q->is_main_query() )
1700 1830 return $join;
1701 1831
1702 - if ( !isset($_GET['s']) || ( isset($_GET['s']) && ph_clean($_GET['s']) == '' ) )
1832 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1833 + $search = isset( $_GET['s'] ) && is_string( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
1834 + if ( $search === '' ) {
1703 1835 return $join;
1836 + }
1704 1837
1705 1838 if ( 'property' === $typenow )
1706 1839 {
1707 1840 $join .= "
@@ -1712,11 +1845,13 @@
1712 1845 }
1713 1846 elseif ( 'contact' === $typenow )
1714 1847 {
1715 1848 $phone_number = '';
1716 - if ( is_numeric(substr(ph_clean($_GET['s']), 0, 1)) )
1849 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1850 + if ( is_numeric(substr($search, 0, 1)) )
1717 1851 {
1718 - $phone_number = preg_replace( "/[^0-9,]/", "", ph_clean($_GET['s']) );
1852 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1853 + $phone_number = preg_replace( "/[^0-9,]/", "", $search );
1719 1854 }
1720 1855
1721 1856 $join .= "
1722 1857 LEFT JOIN " . $wpdb->postmeta . " AS ph_contact_filter_meta_address_concatenated ON " . $wpdb->posts . ".ID = ph_contact_filter_meta_address_concatenated.post_id AND ph_contact_filter_meta_address_concatenated.meta_key = '_address_concatenated'
@@ -1759,35 +1894,43 @@
1759 1894
1760 1895 if ( !$q->is_main_query() )
1761 1896 return $where;
1762 1897
1763 - if ( !isset($_GET['s']) || ( isset($_GET['s']) && ph_clean($_GET['s']) == '' ) )
1898 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1899 + $search = isset( $_GET['s'] ) && is_string( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
1900 + if ( $search === '' ) {
1764 1901 return $where;
1902 + }
1903 + $reference_like = $wpdb->prepare( '%s', $wpdb->esc_like( $search ) . '%' );
1904 + $reference_exact = $wpdb->prepare( '%s', $search );
1905 + $phone_number = '';
1765 1906
1766 1907 if ( 'property' === $typenow )
1767 1908 {
1768 - $where = preg_replace(
1769 - "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
1770 - "(
1771 - (" . $wpdb->posts . ".post_title LIKE $1)
1909 + $where = preg_replace_callback(
1910 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1911 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1912 + return "(
1913 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1772 1914 OR
1773 - (ph_property_filter_meta_address_concatenated.meta_value LIKE $1)
1915 + (ph_property_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
1774 1916 OR
1775 - (ph_property_filter_meta_reference_number.meta_value LIKE '" . esc_sql($_GET['s']) . "%')
1917 + (ph_property_filter_meta_reference_number.meta_value LIKE " . $reference_like . ")
1776 1918 OR
1777 - (ph_property_filter_meta_owner_details.meta_value LIKE $1)
1778 - )",
1919 + (ph_property_filter_meta_owner_details.meta_value LIKE " . $matches[1] . ")
1920 + )";
1921 + },
1779 1922 $where
1780 1923 );
1781 1924
1782 1925 $where = preg_replace(
1783 - "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*(\'[^\']+\')\s*\)/",
1926 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1784 1927 "",
1785 1928 $where
1786 1929 );
1787 1930
1788 1931 $where = preg_replace(
1789 - "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*(\'[^\']+\')\s*\)/",
1932 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1790 1933 "",
1791 1934 $where
1792 1935 );
1793 1936 }
@@ -1793,34 +1936,38 @@
1793 1936 }
1794 1937 elseif ( 'contact' === $typenow )
1795 1938 {
1796 1939 $phone_number = '';
1797 - if ( is_numeric(substr(ph_clean($_GET['s']), 0, 1)) )
1940 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1941 + if ( is_numeric(substr($search, 0, 1)) )
1798 1942 {
1799 - $phone_number = preg_replace( "/[^0-9,]/", "", ph_clean($_GET['s']) );
1943 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list display or query; no state change.
1944 + $phone_number = preg_replace( "/[^0-9,]/", "", $search );
1800 1945 }
1801 1946
1802 - $where = preg_replace(
1803 - "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
1804 - "(
1805 - (" . $wpdb->posts . ".post_title LIKE $1)
1947 + $where = preg_replace_callback(
1948 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1949 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1950 + return "(
1951 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1806 1952 OR
1807 - (ph_contact_filter_meta_address_concatenated.meta_value LIKE $1)
1953 + (ph_contact_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
1808 1954 OR
1809 - (ph_contact_filter_meta_email_address.meta_value LIKE $1)
1955 + (ph_contact_filter_meta_email_address.meta_value LIKE " . $matches[1] . ")
1810 1956 " . ( $phone_number != '' ? "OR (ph_contact_filter_meta_telephone_number.meta_value LIKE '%" . $phone_number . "%')" : '' ) . "
1811 - )",
1957 + )";
1958 + },
1812 1959 $where
1813 1960 );
1814 1961
1815 1962 $where = preg_replace(
1816 - "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*(\'[^\']+\')\s*\)/",
1963 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_excerpt\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1817 1964 "",
1818 1965 $where
1819 1966 );
1820 1967
1821 1968 $where = preg_replace(
1822 - "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*(\'[^\']+\')\s*\)/",
1969 + "/\s+OR\s+\(\s*" . $wpdb->posts . ".post_content\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1823 1970 "",
1824 1971 $where
1825 1972 );
1826 1973 }
@@ -1825,43 +1972,47 @@
1825 1972 );
1826 1973 }
1827 1974 elseif ( 'appraisal' === $typenow )
1828 1975 {
1829 - $where = preg_replace(
1830 - "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
1831 - "(
1832 - (" . $wpdb->posts . ".post_title LIKE $1)
1976 + $where = preg_replace_callback(
1977 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
1978 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
1979 + return "(
1980 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1833 1981 OR
1834 - (ph_appraisal_filter_meta_name_number.meta_value LIKE $1)
1982 + (ph_appraisal_filter_meta_name_number.meta_value LIKE " . $matches[1] . ")
1835 1983 OR
1836 - (ph_appraisal_filter_meta_street.meta_value LIKE $1)
1984 + (ph_appraisal_filter_meta_street.meta_value LIKE " . $matches[1] . ")
1837 1985 OR
1838 - (ph_appraisal_filter_meta_2.meta_value LIKE $1)
1986 + (ph_appraisal_filter_meta_2.meta_value LIKE " . $matches[1] . ")
1839 1987 OR
1840 - (ph_appraisal_filter_meta_3.meta_value LIKE $1)
1988 + (ph_appraisal_filter_meta_3.meta_value LIKE " . $matches[1] . ")
1841 1989 OR
1842 - (ph_appraisal_filter_meta_4.meta_value LIKE $1)
1990 + (ph_appraisal_filter_meta_4.meta_value LIKE " . $matches[1] . ")
1843 1991 OR
1844 - (ph_appraisal_filter_meta_postcode.meta_value LIKE $1)
1845 - )",
1992 + (ph_appraisal_filter_meta_postcode.meta_value LIKE " . $matches[1] . ")
1993 + )";
1994 + },
1846 1995 $where
1847 1996 );
1848 1997 }
1849 1998 elseif ( 'viewing' === $typenow || 'offer' === $typenow || 'sale' === $typenow || 'tenancy' === $typenow )
1850 1999 {
1851 - $where = preg_replace(
1852 - "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
1853 - "(
1854 - (" . $wpdb->posts . ".post_title LIKE $1)
2000 + $where = preg_replace_callback(
2001 + "/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*('(?:\\\\.|[^'\\\\])*')\s*\)/",
2002 + static function( $matches ) use ( $wpdb, $reference_like, $reference_exact, $phone_number ) {
2003 + return "(
2004 + (" . $wpdb->posts . ".post_title LIKE " . $matches[1] . ")
1855 2005 OR
1856 - (ph_property_filter_posts.post_title LIKE $1)
2006 + (ph_property_filter_posts.post_title LIKE " . $matches[1] . ")
1857 2007 OR
1858 - (ph_property_filter_meta_address_concatenated.meta_value LIKE $1)
2008 + (ph_property_filter_meta_address_concatenated.meta_value LIKE " . $matches[1] . ")
1859 2009 OR
1860 - (ph_property_filter_meta_reference_number.meta_value = '" . esc_sql($_GET['s']) . "')
2010 + (ph_property_filter_meta_reference_number.meta_value = " . $reference_exact . ")
1861 2011 OR
1862 - (ph_applicant_filter_posts.post_title LIKE $1)
1863 - )",
2012 + (ph_applicant_filter_posts.post_title LIKE " . $matches[1] . ")
2013 + )";
2014 + },
1864 2015 $where
1865 2016 );
1866 2017 }
1867 2018
@@ -1932,5 +2083,5 @@
1932 2083 }
1933 2084
1934 2085 endif;
1935 2086
1936 -return new PH_Admin_Post_Types();
2087 +return new PH_Admin_Post_Types();