PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
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 1.4.61 All 261 releases
← All changes | includes/admin/settings/class-ph-settings-offices.php +86 -27 2.2.22.3.1 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 Office Settings
4 7 *
5 8 * @author PropertyHive
@@ -14,8 +17,9 @@
14 17
15 18 /**
16 19 * PH_Settings_Offices
17 20 */
21 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Settings_Offices; preserving the existing PH_* class name is required for plugin and extension compatibility.
18 22 class PH_Settings_Offices extends PH_Settings_Page {
19 23
20 24 /**
21 25 * Constructor.
@@ -29,8 +33,27 @@
29 33 add_action( 'propertyhive_settings_save_' . $this->id, array( $this, 'save' ) );
30 34 add_action( 'propertyhive_sections_' . $this->id, array( $this, 'output_sections' ) );
31 35 add_action( 'propertyhive_admin_field_offices', array( $this, 'offices_setting' ) );
32 36 }
37 +
38 + /**
39 + * Read one scalar value from the verified office settings form.
40 + *
41 + * @param string $key Posted field name.
42 + * @return string
43 + */
44 + private function get_posted_text( $key ) {
45 + // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- save() verifies the settings nonce and manage_options before calling this helper; arrays are rejected before the scalar is copied.
46 + if ( ! isset( $_POST[ $key ] ) || ! is_scalar( $_POST[ $key ] ) ) {
47 + return '';
48 + }
49 +
50 + // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- save() verifies the settings nonce before this helper is called; the copied scalar is unslashed immediately below and sanitized before use.
51 + $raw_value = $_POST[ $key ];
52 + $raw_value = wp_unslash( (string) $raw_value );
53 +
54 + return ph_clean( $raw_value );
55 + }
33 56
34 57 /**
35 58 * Get settings array
36 59 *
@@ -59,13 +82,14 @@
59 82 public function get_office_settings() {
60 83
61 84 global $current_section;
62 85
63 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
86 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only settings selection; no state change occurs while building the form.
87 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
64 88
65 89 $args = array(
66 90
67 - array( 'title' => __( ( $current_section == 'add' ? 'Add New Office' : 'Edit Office Details' ), 'propertyhive' ), 'type' => 'title', 'desc' => '', 'id' => 'office_options' ),
91 + array( 'title' => ( $current_section == 'add' ? __( 'Add New Office', 'propertyhive' ) : __( 'Edit Office Details', 'propertyhive' ) ), 'type' => 'title', 'desc' => '', 'id' => 'office_options' ),
68 92
69 93 array(
70 94 'title' => __( 'Office Name', 'propertyhive' ),
71 95 'id' => 'office_name',
@@ -132,9 +156,13 @@
132 156 {
133 157 if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
134 158 {
135 159 $args[] = array(
136 - 'title' => sprintf( __( 'Telephone Number (%s)', 'propertyhive' ), $value ),
160 + 'title' => sprintf(
161 + /* translators: %s: department (e.g. Sales, Lettings, Commercial) */
162 + __( 'Telephone Number (%s)', 'propertyhive' ),
163 + $value
164 + ),
137 165 'id' => '_office_telephone_number_' . str_replace("residential-", "", $key),
138 166 //'css' => 'width:50px;',
139 167 'default' => get_post_meta($current_id, '_office_telephone_number_' . str_replace("residential-", "", $key), TRUE),
140 168 'type' => 'text',
@@ -141,9 +169,13 @@
141 169 'desc_tip' => false,
142 170 );
143 171
144 172 $args[] = array(
145 - 'title' => sprintf( __( 'Email Address (%s)', 'propertyhive' ), $value ),
173 + 'title' => sprintf(
174 + /* translators: %s: department (e.g. Sales, Lettings, Commercial) */
175 + __( 'Email Address (%s)', 'propertyhive' ),
176 + $value
177 + ),
146 178 'id' => '_office_email_address_' . str_replace("residential-", "", $key),
147 179 //'css' => 'width:50px;',
148 180 'default' => get_post_meta($current_id, '_office_email_address_' . str_replace("residential-", "", $key), TRUE),
149 181 'type' => 'text',
@@ -188,17 +220,22 @@
188 220 public function get_office_delete() {
189 221
190 222 global $save_button_text, $post;
191 223
224 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
192 225 $save_button_text = __( 'Delete', 'propertyhive' );
193 226
227 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This POST field only selects the delete confirmation display; save() performs the mutation after nonce and capability checks.
194 228 if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == 1 )
195 229 {
196 230 // A term has just been deleted
197 231 global $hide_save_button, $show_cancel_button, $cancel_button_href;
198 232
233 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
199 234 $hide_save_button = TRUE;
235 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
200 236 $show_cancel_button = TRUE;
237 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
201 238 $cancel_button_href = admin_url( 'admin.php?page=ph-settings&tab=offices' );
202 239
203 240 $args = array();
204 241
@@ -215,9 +252,10 @@
215 252 $args[] = array( 'type' => 'sectionend', 'id' => 'office_delete' );
216 253 }
217 254 else
218 255 {
219 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
256 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only settings selection; no state change occurs while building the form.
257 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
220 258
221 259 if ($current_id == '')
222 260 {
223 261 die("ID not passed");
@@ -236,8 +274,9 @@
236 274 $query_args = array(
237 275 'post_type' => 'property',
238 276 'nopaging' => true,
239 277 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
278 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Count all properties assigned through _office_id across the existing statuses before offering office deletion/reassignment choices.
240 279 'meta_query' => array(
241 280 array(
242 281 'key' => '_office_id',
243 282 'value' => $current_id,
@@ -252,13 +291,14 @@
252 291 // Get number of applicants assigned to this term (future)
253 292
254 293 if ($num_properties > 0)
255 294 {
256 - $alternative_offices = array();
295 + $alternative_terms = array();
257 296
258 297 $query_args = array(
259 298 'post_type' => 'office',
260 299 'nopaging' => true,
300 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- The delete form must offer every other office as a reassignment target and must exclude the office currently being deleted.
261 301 'post__not_in' => array( $current_id ),
262 302 'orderby' => 'title',
263 303 'order' => 'ASC'
264 304 );
@@ -317,8 +357,9 @@
317 357 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
318 358
319 359 $settings = $this->get_office_settings();
320 360
361 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
321 362 $redirect_after_save = admin_url('admin.php?page=ph-settings&tab=offices');
322 363
323 364 PH_Admin_Settings::output_fields( $settings );
324 365
@@ -325,12 +366,14 @@
325 366 } elseif ( $current_section == 'edit' ) {
326 367
327 368 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
328 369
329 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
370 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only settings selection; no state change occurs while building the form.
371 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
330 372
331 373 $settings = $this->get_office_settings();
332 374
375 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
333 376 $redirect_after_save = admin_url('admin.php?page=ph-settings&tab=offices');
334 377
335 378 PH_Admin_Settings::output_fields( $settings );
336 379
@@ -337,9 +380,10 @@
337 380 } elseif ( $current_section == 'delete' ) {
338 381
339 382 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
340 383
341 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
384 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only settings selection; no state change occurs while building the form.
385 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
342 386
343 387 $settings = $this->get_office_delete();
344 388
345 389 PH_Admin_Settings::output_fields( $settings );
@@ -436,9 +480,9 @@
436 480 <td class="address">
437 481 ' . esc_html($address) . '
438 482 </td>
439 483 <td class="contact">
440 - ' . $contact_details . '
484 + ' . wp_kses_post( $contact_details ) . '
441 485 </td>';
442 486 do_action( 'propertyhive_office_table_row_columns', get_the_ID() );
443 487 echo '
444 488 <td class="settings">
@@ -473,10 +517,22 @@
473 517 /**
474 518 * Save settings
475 519 */
476 520 public function save() {
521 + if ( ! current_user_can( 'manage_options' ) || ! isset( $_REQUEST['_wpnonce'] ) || ! is_string( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'propertyhive-settings' ) ) {
522 + return;
523 + }
524 +
477 525 global $current_section, $post;
478 526
527 + if ( in_array( $current_section, array( 'edit', 'delete' ), true ) ) {
528 + $office_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
529 + if ( ! $office_id || 'office' !== get_post_type( $office_id ) ) {
530 + PH_Admin_Settings::add_error( __( 'Please select a valid office.', 'propertyhive' ) );
531 + return;
532 + }
533 + }
534 +
479 535 if ( $current_section == 'add' ) {
480 536
481 537 // TODO: Validate (check for blank fields, and that office name doest exist already)
482 538
@@ -481,9 +537,9 @@
481 537 // TODO: Validate (check for blank fields, and that office name doest exist already)
482 538
483 539 // Insert office
484 540 $office_post = array(
485 - 'post_title' => ph_clean( $_POST['office_name'] ),
541 + 'post_title' => wp_slash( $this->get_posted_text( 'office_name' ) ),
486 542 'post_content' => '',
487 543 'post_status' => 'publish',
488 544 'post_type' => 'office',
489 545 'comment_status' => 'closed',
@@ -499,18 +555,17 @@
499 555 PH_Admin_Settings::add_message( __( 'Office added successfully', 'propertyhive' ) . ' ' . '<a href="' . admin_url( 'admin.php?page=ph-settings&tab=offices' ) . '">' . __( 'Return to offices', 'propertyhive' ) . '</a>' );
500 556
501 557 } elseif ( $current_section == 'edit' ) {
502 558
503 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
559 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
504 560
505 561 // TODO: Validate
506 - // TODO: Make sure this ID belongs to an office
507 562 // TODO: Update slug?
508 563
509 564 // Update office
510 565 $office_post = array(
511 566 'ID' => $current_id,
512 - 'post_title' => ph_clean( $_POST['office_name'] )
567 + 'post_title' => wp_slash( $this->get_posted_text( 'office_name' ) )
513 568 );
514 569
515 570 wp_update_post( $office_post );
516 571
@@ -522,11 +577,11 @@
522 577 PH_Admin_Settings::add_message( __( 'Office details updated successfully', 'propertyhive' ) . ' ' . '<a href="' . admin_url( 'admin.php?page=ph-settings&tab=offices' ) . '">' . __( 'Return to offices', 'propertyhive' ) . '</a>' );
523 578
524 579 } elseif ( $current_section == 'delete' ) {
525 580
526 - if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == '1' )
581 + if ( '1' === $this->get_posted_text( 'confirm_removal' ) )
527 582 {
528 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
583 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
529 584
530 585 // Get number of properties assigned to this term
531 586 $query_args = array(
532 587 'post_type' => 'property',
@@ -531,8 +586,9 @@
531 586 $query_args = array(
532 587 'post_type' => 'property',
533 588 'nopaging' => true,
534 589 'post_status' => array( 'pending', 'auto-draft', 'draft', 'private', 'publish', 'future', 'trash' ),
590 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Office deletion must find every property assigned to this office so each one can be reassigned before the office is removed; the office ID is read from the verified admin form.
535 591 'meta_query' => array(
536 592 array(
537 593 'key' => '_office_id',
538 594 'value' => $current_id,
@@ -549,9 +605,10 @@
549 605 die("Not assigning properties to new office. Please try again");
550 606 }
551 607 else
552 608 {
553 - $post_type = get_post_type( (int)$_POST['reassign_to'] );
609 + $reassign_to = absint( $this->get_posted_text( 'reassign_to' ) );
610 + $post_type = get_post_type( $reassign_to );
554 611
555 612 if ( $post_type != 'office' )
556 613 {
557 614 die("New office isn't of type office. It's of type: " . esc_html($post_type));
@@ -561,9 +618,9 @@
561 618 while ( $property_query->have_posts() )
562 619 {
563 620 $property_query->the_post();
564 621
565 - update_post_meta( $post->ID, '_office_id', (int)$_POST['reassign_to'] );
622 + update_post_meta( $post->ID, '_office_id', $reassign_to );
566 623
567 624 // TODO: Check for WP_ERROR
568 625 }
569 626 }
@@ -602,17 +659,17 @@
602 659
603 660 wp_reset_postdata();
604 661
605 662 // Set selected office as primary
606 - update_post_meta( (int)$_POST['primary'], 'primary', '1');
663 + update_post_meta( absint( $this->get_posted_text( 'primary' ) ), 'primary', '1');
607 664 }
608 665 else
609 666 {
610 - update_post_meta($office_post_id, '_office_address_1', ph_clean( $_POST['_office_address_1'] ));
611 - update_post_meta($office_post_id, '_office_address_2', ph_clean( $_POST['_office_address_2'] ));
612 - update_post_meta($office_post_id, '_office_address_3', ph_clean( $_POST['_office_address_3'] ));
613 - update_post_meta($office_post_id, '_office_address_4', ph_clean( $_POST['_office_address_4'] ));
614 - update_post_meta($office_post_id, '_office_address_postcode', ph_clean( $_POST['_office_address_postcode'] ));
667 + update_post_meta($office_post_id, '_office_address_1', wp_slash( $this->get_posted_text( '_office_address_1' ) ));
668 + update_post_meta($office_post_id, '_office_address_2', wp_slash( $this->get_posted_text( '_office_address_2' ) ));
669 + update_post_meta($office_post_id, '_office_address_3', wp_slash( $this->get_posted_text( '_office_address_3' ) ));
670 + update_post_meta($office_post_id, '_office_address_4', wp_slash( $this->get_posted_text( '_office_address_4' ) ));
671 + update_post_meta($office_post_id, '_office_address_postcode', wp_slash( $this->get_posted_text( '_office_address_postcode' ) ));
615 672
616 673 $departments = ph_get_departments();
617 674
618 675 foreach ( $departments as $key => $value )
@@ -618,15 +675,17 @@
618 675 foreach ( $departments as $key => $value )
619 676 {
620 677 if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
621 678 {
622 - update_post_meta($office_post_id, '_office_telephone_number_' . str_replace("residential-", "", $key), (isset($_POST['_office_telephone_number_' . str_replace("residential-", "", $key)])) ? ph_clean( $_POST['_office_telephone_number_' . str_replace("residential-", "", $key)] ) : '');
623 - update_post_meta($office_post_id, '_office_email_address_' . str_replace("residential-", "", $key), (isset($_POST['_office_email_address_' . str_replace("residential-", "", $key)])) ? ph_clean( $_POST['_office_email_address_' . str_replace("residential-", "", $key)] ) : '');
679 + $ph_contact_telephone_value = ( isset( $_POST['_office_telephone_number_' . str_replace("residential-", "", $key)] ) && is_string( $_POST['_office_telephone_number_' . str_replace("residential-", "", $key)] ) ) ? sanitize_text_field( wp_unslash( $_POST['_office_telephone_number_' . str_replace("residential-", "", $key)] ) ) : '';
680 + update_post_meta( $office_post_id, '_office_telephone_number_' . str_replace("residential-", "", $key), wp_slash( $ph_contact_telephone_value ) );
681 + $ph_contact_email_value = ( isset( $_POST['_office_email_address_' . str_replace("residential-", "", $key)] ) && is_string( $_POST['_office_email_address_' . str_replace("residential-", "", $key)] ) ) ? sanitize_text_field( wp_unslash( $_POST['_office_email_address_' . str_replace("residential-", "", $key)] ) ) : '';
682 + update_post_meta( $office_post_id, '_office_email_address_' . str_replace("residential-", "", $key), wp_slash( $ph_contact_email_value ) );
624 683 }
625 684 }
626 685
627 - update_post_meta($office_post_id, '_office_latitude', ph_clean( $_POST['_office_latitude'] ));
628 - update_post_meta($office_post_id, '_office_longitude', ph_clean( $_POST['_office_longitude'] ));
686 + update_post_meta($office_post_id, '_office_latitude', wp_slash( $this->get_posted_text( '_office_latitude' ) ));
687 + update_post_meta($office_post_id, '_office_longitude', wp_slash( $this->get_posted_text( '_office_longitude' ) ));
629 688
630 689 do_action( 'propertyhive_save_office', $office_post_id );
631 690 }
632 691 }