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