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-matching-applicants.php +121 -52 2.2.2 → 2.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 * PropertyHive Admin Matching Applicants Class.
4 7 *
5 8 * @author PropertyHive
@@ -14,27 +17,40 @@
14 17
15 18 /**
16 19 * PH_Admin_Matching_Applicants
17 20 */
21 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Matching_Applicants; preserving the existing PH_* class name is required for plugin and extension compatibility.
18 22 class PH_Admin_Matching_Applicants {
19 23
20 24 public function output()
21 25 {
22 - if ( !isset($_GET['property_id']) || (isset($_GET['property_id']) && get_post_type((int)$_GET['property_id']) != 'property') )
23 - {
24 - die('Invalid property_id passed');
25 - }
26 + // The initial matching screen is read-only. The POST branch below verifies the
27 + // matching nonce before it performs any state-changing action.
28 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This request is used to render the read-only matching screen; POST mutations verify the matching nonce below.
29 + $request_get = wp_unslash( $_GET );
30 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only the presence of the action selector is checked here; request values are normalized after the nonce check below.
31 + $has_step = isset( $_POST['step'] );
26 32
27 - $property_id = (int)$_GET['property_id'];
33 + $property_id = ( isset( $request_get['property_id'] ) && is_scalar( $request_get['property_id'] ) ) ? absint( $request_get['property_id'] ) : 0;
28 34
29 - $property = new PH_Property($property_id);
35 + if ( ! $property_id || get_post_type( $property_id ) !== 'property' )
36 + {
37 + die('Invalid property_id passed');
38 + }
30 39
31 - if ( isset($_POST['step']) )
40 + $property = new PH_Property($property_id);
41 +
42 + if ( $has_step )
32 43 {
33 - if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'propertyhive-matching-applicants' ) )
44 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only the nonce value is read before verification; all other POST values are normalized after the check below.
45 + $request_request = wp_unslash( $_REQUEST );
46 + if ( empty( $request_request['_wpnonce'] ) || ! wp_verify_nonce( ( isset( $request_request['_wpnonce'] ) && is_string( $request_request['_wpnonce'] ) ) ? sanitize_text_field( $request_request['_wpnonce'] ) : '', 'propertyhive-matching-applicants' ) )
34 47 die( esc_html(__( 'Action failed. Please refresh the page and retry.', 'propertyhive' )) );
35 48
36 - switch ( $_POST['step'] )
49 + $request_post = wp_unslash( $_POST );
50 + $step = is_string( $request_post['step'] ) ? sanitize_key( $request_post['step'] ) : '';
51 +
52 + switch ( $step )
37 53 {
38 54 case "one":
39 55 {
40 56 // Properties have been selected to email or dismiss
@@ -44,9 +60,9 @@
44 60
45 61 $nothing_to_send = true;
46 62
47 63 // Handle properties to email
48 - if ( isset($_POST['email_contact_applicant_profile_id']) && !empty($_POST['email_contact_applicant_profile_id']) )
64 + if ( isset( $request_post['email_contact_applicant_profile_id'] ) && ! empty( $request_post['email_contact_applicant_profile_id'] ) )
49 65 {
50 66 $nothing_to_send = false;
51 67
52 68 $subject = get_option( 'propertyhive_property_match_default_email_subject', '' );
@@ -74,9 +90,9 @@
74 90 <div id="poststuff">
75 91
76 92 <form method="post" id="mainform" action="" enctype="multipart/form-data">
77 93 <?php
78 - if ( isset($_POST['email_contact_applicant_profile_id']) && !empty($_POST['email_contact_applicant_profile_id']) )
94 + if ( isset( $request_post['email_contact_applicant_profile_id'] ) && ! empty( $request_post['email_contact_applicant_profile_id'] ) )
79 95 {
80 96 // We've got emails to send
81 97 include 'views/html-admin-matching-applicants-email.php';
82 98 }
@@ -85,14 +101,27 @@
85 101 ?>
86 102 <p class="submit">
87 103
88 104 <input name="save" class="button-primary" type="submit" value="<?php echo esc_attr(__( 'Send Matches', 'propertyhive' )); ?>" />
89 - <?php if ( isset($_POST['email_contact_applicant_profile_id']) && !empty($_POST['email_contact_applicant_profile_id']) ) { ?>
105 + <?php if ( isset( $request_post['email_contact_applicant_profile_id'] ) && ! empty( $request_post['email_contact_applicant_profile_id'] ) ) { ?>
90 106 <input name="preview" id="preview_email" class="button" type="button" value="<?php echo esc_attr(__( 'Preview Email', 'propertyhive' )); ?>" />
91 107 <?php } ?>
92 108
93 109 <input type="hidden" name="step" value="two" />
94 - <input type="hidden" name="email_contact_applicant_profile_id" value="<?php echo ( isset($_POST['email_contact_applicant_profile_id']) && is_array($_POST['email_contact_applicant_profile_id']) && !empty($_POST['email_contact_applicant_profile_id']) ) ? esc_attr(implode(",", ph_clean($_POST['email_contact_applicant_profile_id']))) : ''; ?>" />
110 + <input type="hidden" name="email_contact_applicant_profile_id" value="<?php
111 + $selected_contact_applicant_profile_ids = array();
112 + if ( isset( $request_post['email_contact_applicant_profile_id'] ) && is_array( $request_post['email_contact_applicant_profile_id'] ) ) {
113 + foreach ( $request_post['email_contact_applicant_profile_id'] as $selected_contact_applicant_profile_id ) {
114 + if ( is_string( $selected_contact_applicant_profile_id ) ) {
115 + $parts = explode( '|', $selected_contact_applicant_profile_id );
116 + if ( count( $parts ) >= 2 ) {
117 + $selected_contact_applicant_profile_ids[] = absint( $parts[0] ) . '|' . absint( $parts[1] );
118 + }
119 + }
120 + }
121 + }
122 + echo esc_attr( implode( ',', $selected_contact_applicant_profile_ids ) );
123 + ?>" />
95 124 <?php do_action( 'propertyhive_applicant_match_step_two_hidden_fields' ); ?>
96 125 <?php wp_nonce_field( 'propertyhive-matching-applicants' ); ?>
97 126
98 127 </p>
@@ -97,9 +126,20 @@
97 126
98 127 </p>
99 128
100 129 <p>
101 - <?php echo __( 'When sending out lots of emails we recommend using <a href="https://en-gb.wordpress.org/plugins/tags/smtp" target="_blank">a plugin</a> to send them out using SMTP. Your web developer or hosting company should be able to advise on this.', 'propertyhive' );
130 + <?php
131 + echo wp_kses_post(
132 + sprintf(
133 + /* translators: 1: Opening link tag to the WordPress.org SMTP plugins page, 2: Closing link tag. */
134 + __(
135 + 'When sending out lots of emails we recommend using %1$sa plugin%2$s to send them out using SMTP. Your web developer or hosting company should be able to advise on this.',
136 + 'propertyhive'
137 + ),
138 + '<a href="https://en-gb.wordpress.org/plugins/tags/smtp" target="_blank" rel="noopener noreferrer">',
139 + '</a>'
140 + )
141 + );
102 142 ?>
103 143 </p>
104 144
105 145 </form>
@@ -122,9 +162,9 @@
122 162
123 163 function showPreview()
124 164 {
125 165 jQuery('#mainform').attr('target', '_blank');
126 - jQuery('#mainform').attr('action', '<?php echo admin_url( '?preview_propertyhive_email=true&property_id=' . (int)$_GET['property_id']); ?>');
166 + jQuery('#mainform').attr('action', <?php echo wp_json_encode( admin_url( '?preview_propertyhive_email=true&property_id=' . $property_id ), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>);
127 167
128 168 jQuery('#mainform').submit();
129 169 jQuery('#mainform').attr('target', '_self');
130 170 jQuery('#mainform').attr('action', '');
@@ -135,9 +175,9 @@
135 175 }
136 176
137 177 if ( $nothing_to_send == true )
138 178 {
139 - echo '<script>window.location.href = "' . get_edit_post_link( $property_id, 'url' ) . '&ph_message=2";</script>';
179 + echo '<script>window.location.href = ' . wp_json_encode( get_edit_post_link( $property_id, 'url' ) . '&ph_message=2', JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';</script>';
140 180
141 181 //header("Location: " . get_edit_post_link( $contact_id, 'url' ) . '&ph_message=2' ); // properties marked as not interested
142 182 //die();
143 183 }
@@ -145,17 +185,24 @@
145 185 break;
146 186 }
147 187 case "two":
148 188 {
149 - if ( isset($_POST['email_contact_applicant_profile_id']) && !empty($_POST['email_contact_applicant_profile_id']) )
150 - {
151 - $email_contact_applicant_profile_id = explode(",", sanitize_text_field($_POST['email_contact_applicant_profile_id']));
189 + if ( isset( $request_post['email_contact_applicant_profile_id'] ) && ! empty( $request_post['email_contact_applicant_profile_id'] ) )
190 + {
191 + $email_contact_applicant_profile_id_input = ( isset( $request_post['email_contact_applicant_profile_id'] ) && is_string( $request_post['email_contact_applicant_profile_id'] ) ) ? $request_post['email_contact_applicant_profile_id'] : '';
192 + $email_contact_applicant_profile_id = explode( ',', sanitize_text_field( $email_contact_applicant_profile_id_input ) );
152 193
153 - foreach ( $email_contact_applicant_profile_id as $contact_applicant_profile_id )
154 - {
155 - $explode_contact_applicant_profile_id = explode("|", $contact_applicant_profile_id);
156 - $contact_id = $explode_contact_applicant_profile_id[0];
157 - $applicant_profile_id = $explode_contact_applicant_profile_id[1];
194 + foreach ( $email_contact_applicant_profile_id as $contact_applicant_profile_id )
195 + {
196 + $explode_contact_applicant_profile_id = explode("|", $contact_applicant_profile_id);
197 + if ( count( $explode_contact_applicant_profile_id ) < 2 ) {
198 + continue;
199 + }
200 + $contact_id = absint( $explode_contact_applicant_profile_id[0] );
201 + $applicant_profile_id = absint( $explode_contact_applicant_profile_id[1] );
202 + if ( ! $contact_id ) {
203 + continue;
204 + }
158 205
159 206 $email_address = get_post_meta( (int)$contact_id, '_email_address', TRUE );
160 207
161 208 $to_email_addresses = explode(",", $email_address);
@@ -164,9 +211,10 @@
164 211 {
165 212 $new_to_email_addresses[] = sanitize_email($to_email_address);
166 213 }
167 214
168 - $cc_email_addresses = explode(",", $_POST['cc_email_address']);
215 + $cc_email_address_input = ( isset( $request_post['cc_email_address'] ) && is_string( $request_post['cc_email_address'] ) ) ? $request_post['cc_email_address'] : '';
216 + $cc_email_addresses = explode( ',', $cc_email_address_input );
169 217 $new_cc_email_addresses = array();
170 218 foreach ( $cc_email_addresses as $cc_email_address )
171 219 {
172 220 $new_cc_email_addresses[] = sanitize_email($cc_email_address);
@@ -171,9 +219,10 @@
171 219 {
172 220 $new_cc_email_addresses[] = sanitize_email($cc_email_address);
173 221 }
174 222
175 - $bcc_email_addresses = explode(",", $_POST['bcc_email_address']);
223 + $bcc_email_address_input = ( isset( $request_post['bcc_email_address'] ) && is_string( $request_post['bcc_email_address'] ) ) ? $request_post['bcc_email_address'] : '';
224 + $bcc_email_addresses = explode( ',', $bcc_email_address_input );
176 225 $new_bcc_email_addresses = array();
177 226 foreach ( $bcc_email_addresses as $bcc_email_address )
178 227 {
179 228 $new_bcc_email_addresses[] = sanitize_email($bcc_email_address);
@@ -198,9 +247,13 @@
198 247 ),
199 248 );
200 249 $allowed_tags = apply_filters( 'propertyhive_match_email_allowed_tags', $allowed_tags );
201 250
202 - $body = wp_kses(wp_unslash($_POST['body']), $allowedposttags);
251 + $body_input = ( isset( $request_post['body'] ) && is_string( $request_post['body'] ) ) ? $request_post['body'] : '';
252 + $body = wp_kses( $body_input, $allowed_tags );
253 + $from_name_input = ( isset( $request_post['from_name'] ) && is_string( $request_post['from_name'] ) ) ? $request_post['from_name'] : '';
254 + $from_email_address_input = ( isset( $request_post['from_email_address'] ) && is_string( $request_post['from_email_address'] ) ) ? $request_post['from_email_address'] : '';
255 + $subject_input = ( isset( $request_post['subject'] ) && is_string( $request_post['subject'] ) ) ? $request_post['subject'] : '';
203 256
204 257 // Email info entered. Time to send emails
205 258 $this->send_emails(
206 259 (int)$contact_id,
@@ -205,11 +258,11 @@
205 258 $this->send_emails(
206 259 (int)$contact_id,
207 260 (int)$applicant_profile_id,
208 261 array($property_id),
209 - ph_clean(wp_unslash($_POST['from_name'])),
210 - sanitize_email(wp_unslash($_POST['from_email_address'])),
211 - ph_clean(wp_unslash($_POST['subject'])),
262 + ph_clean( $from_name_input ),
263 + sanitize_email( $from_email_address_input ),
264 + ph_clean( $subject_input ),
212 265 $body,
213 266 implode(",", $new_to_email_addresses),
214 267 implode(",", $new_cc_email_addresses),
215 268 implode(",", $new_bcc_email_addresses)
@@ -221,15 +274,15 @@
221 274 }
222 275
223 276 do_action( 'propertyhive_applicant_match_step_send', $property_id );
224 277
225 - echo '<script>window.location.href = "' . get_edit_post_link( $property_id, 'url' ) . '&ph_message=1";</script>';
278 + echo '<script>window.location.href = ' . wp_json_encode( get_edit_post_link( $property_id, 'url' ) . '&ph_message=1', JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';</script>';
226 279 }
227 280 }
228 281 }
229 282 else
230 283 {
231 - $applicants = $this->get_matching_applicants( (int)$_GET['property_id'] );
284 + $applicants = $this->get_matching_applicants( $property_id );
232 285
233 286 $on_market_change_date = $property->_on_market_change_date;
234 287 $price_change_date = $property->_price_change_date;
235 288
@@ -236,20 +289,34 @@
236 289 include 'views/html-admin-matching-applicants.php';
237 290 }
238 291 }
239 292
240 - private function dismiss_properties()
241 - {
242 - $property_id = (int)$_GET['property_id'];
293 + private function dismiss_properties()
294 + {
295 + // output() verifies the matching nonce before calling this private mutator.
296 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This private helper is only called from output() after the matching nonce has been verified.
297 + $request_post = wp_unslash( $_POST );
298 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This private helper receives the read-only property identifier from the already-authorized matching screen.
299 + $request_get = wp_unslash( $_GET );
300 + $property_id = ( isset( $request_get['property_id'] ) && is_scalar( $request_get['property_id'] ) ) ? absint( $request_get['property_id'] ) : 0;
243 301
244 - if ( isset($_POST['not_interested_contact_applicant_profile_id']) && !empty($_POST['not_interested_contact_applicant_profile_id']) )
245 - {
246 - foreach ( $_POST['not_interested_contact_applicant_profile_id'] as $contact_applicant_profile_id )
302 + if ( isset( $request_post['not_interested_contact_applicant_profile_id'] ) && is_array( $request_post['not_interested_contact_applicant_profile_id'] ) && ! empty( $request_post['not_interested_contact_applicant_profile_id'] ) )
247 303 {
248 - $explode_contact_applicant_profile_id = explode("|", $contact_applicant_profile_id);
304 + foreach ( $request_post['not_interested_contact_applicant_profile_id'] as $contact_applicant_profile_id )
305 + {
306 + if ( ! is_string( $contact_applicant_profile_id ) ) {
307 + continue;
308 + }
309 + $explode_contact_applicant_profile_id = explode("|", $contact_applicant_profile_id);
310 + if ( count( $explode_contact_applicant_profile_id ) < 2 ) {
311 + continue;
312 + }
249 313
250 - $contact_id = $explode_contact_applicant_profile_id[0];
251 - $applicant_profile_id = $explode_contact_applicant_profile_id[1];
314 + $contact_id = absint( $explode_contact_applicant_profile_id[0] );
315 + $applicant_profile_id = absint( $explode_contact_applicant_profile_id[1] );
316 + if ( ! $contact_id ) {
317 + continue;
318 + }
252 319
253 320 // Get currently dismissed properties for this contact to decide if we need to add or remove it
254 321 $dismissed_properties = get_post_meta( $contact_id, '_dismissed_properties', TRUE );
255 322
@@ -364,8 +431,9 @@
364 431 'value' => 'applicant',
365 432 'compare' => 'LIKE'
366 433 );
367 434
435 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Applicant membership is stored in serialized _contact_types metadata; preserve complete matching results and per-profile extension checks.
368 436 $args['meta_query'] = $meta_query;
369 437
370 438 $contacts_query = new WP_Query( $args );
371 439
@@ -619,10 +687,10 @@
619 687 }
620 688
621 689 $subject = str_replace("[property_count]", count($email_property_ids) . ' propert' . ( ( count($email_property_ids) != 1 ) ? 'ies' : 'y' ), $subject);
622 690
623 - $body = str_replace("[contact_name]", $contact->post_title, $body);
624 - $body = str_replace("[contact_dear]", $contact->dear(), $body);
691 + $body = str_replace( '[contact_name]', esc_html( $contact->post_title ), $body );
692 + $body = str_replace( '[contact_dear]', esc_html( $contact->dear() ), $body );
625 693 $body = str_replace("[property_count]", count($email_property_ids) . ' propert' . ( ( count($email_property_ids) != 1 ) ? 'ies' : 'y' ), $body);
626 694
627 695 $office_counts = array();
628 696
@@ -668,13 +736,13 @@
668 736 $office_name = get_the_title($office_id);
669 737 $office_email_address = get_post_meta( $office_id, '_office_email_address_' . str_replace("residential-", "", $applicant_profile_details['department']), TRUE );
670 738 }
671 739
672 - $body = str_replace("[office_name]", $office_name, $body);
673 - $body = str_replace("[office_email_address]", $office_email_address, $body);
740 + $body = str_replace( '[office_name]', esc_html( $office_name ), $body );
741 + $body = str_replace( '[office_email_address]', esc_html( $office_email_address ), $body );
674 742
675 - $body = str_replace("[negotiator_name]", $current_user->display_name, $body);
676 - $body = str_replace("[negotiator_email_address]", $current_user->user_email, $body);
743 + $body = str_replace( '[negotiator_name]', esc_html( $current_user->display_name ), $body );
744 + $body = str_replace( '[negotiator_email_address]', esc_html( $current_user->user_email ), $body );
677 745
678 746 $body = stripslashes($body);
679 747
680 748 if (extension_loaded('zlib'))
@@ -686,8 +754,9 @@
686 754 }
687 755 }
688 756
689 757 // Insert into email log
758 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Typed insertion into the plugin-owned email queue table; no WordPress object API represents these queued messages.
690 759 $insert = $wpdb->insert(
691 760 $wpdb->prefix . 'ph_email_log',
692 761 array(
693 762 'contact_id' => $contact_id,
@@ -700,9 +769,9 @@
700 769 'from_email_address' => $from_email_address,
701 770 'subject' => stripslashes($subject),
702 771 'body' => $body,
703 772 'status' => '',
704 - 'send_at' => date("Y-m-d H:i:s"),
773 + 'send_at' => gmdate("Y-m-d H:i:s"),
705 774 'sent_by' => $current_user->ID,
706 775 ),
707 776 array(
708 777 '%d',
@@ -741,9 +810,9 @@
741 810 $applicant_profile_match_history[$email_property_id] = array();
742 811 }
743 812
744 813 $applicant_profile_match_history[$email_property_id][] = array(
745 - 'date' => date("Y-m-d H:i:s"),
814 + 'date' => gmdate("Y-m-d H:i:s"),
746 815 'method' => 'email',
747 816 'email_log_id' => $email_log_id,
748 817 );
749 818
@@ -758,9 +827,9 @@
758 827 'comment_post_ID' => $email_property_id,
759 828 'comment_author' => $current_user->display_name,
760 829 'comment_author_email' => '[email protected]',
761 830 'comment_author_url' => '',
762 - 'comment_date' => date("Y-m-d H:i:s"),
831 + 'comment_date' => gmdate("Y-m-d H:i:s"),
763 832 'comment_content' => serialize($comment),
764 833 'comment_approved' => 1,
765 834 'comment_type' => 'propertyhive_note',
766 835 );
@@ -781,9 +850,9 @@
781 850 'comment_post_ID' => $contact_id,
782 851 'comment_author' => $current_user->display_name,
783 852 'comment_author_email' => '[email protected]',
784 853 'comment_author_url' => '',
785 - 'comment_date' => date("Y-m-d H:i:s"),
854 + 'comment_date' => gmdate("Y-m-d H:i:s"),
786 855 'comment_content' => serialize($comment),
787 856 'comment_approved' => 1,
788 857 'comment_type' => 'propertyhive_note',
789 858 );
@@ -793,5 +862,5 @@
793 862 }
794 863
795 864 }
796 865
797 -endif;
866 +endif;