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 +76 -25 2.2.52.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',
@@ -196,17 +220,22 @@
196 220 public function get_office_delete() {
197 221
198 222 global $save_button_text, $post;
199 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.
200 225 $save_button_text = __( 'Delete', 'propertyhive' );
201 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.
202 228 if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == 1 )
203 229 {
204 230 // A term has just been deleted
205 231 global $hide_save_button, $show_cancel_button, $cancel_button_href;
206 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.
207 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.
208 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.
209 238 $cancel_button_href = admin_url( 'admin.php?page=ph-settings&tab=offices' );
210 239
211 240 $args = array();
212 241
@@ -223,9 +252,10 @@
223 252 $args[] = array( 'type' => 'sectionend', 'id' => 'office_delete' );
224 253 }
225 254 else
226 255 {
227 - $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;
228 258
229 259 if ($current_id == '')
230 260 {
231 261 die("ID not passed");
@@ -244,8 +274,9 @@
244 274 $query_args = array(
245 275 'post_type' => 'property',
246 276 'nopaging' => true,
247 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.
248 279 'meta_query' => array(
249 280 array(
250 281 'key' => '_office_id',
251 282 'value' => $current_id,
@@ -260,13 +291,14 @@
260 291 // Get number of applicants assigned to this term (future)
261 292
262 293 if ($num_properties > 0)
263 294 {
264 - $alternative_offices = array();
295 + $alternative_terms = array();
265 296
266 297 $query_args = array(
267 298 'post_type' => 'office',
268 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.
269 301 'post__not_in' => array( $current_id ),
270 302 'orderby' => 'title',
271 303 'order' => 'ASC'
272 304 );
@@ -325,8 +357,9 @@
325 357 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
326 358
327 359 $settings = $this->get_office_settings();
328 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.
329 362 $redirect_after_save = admin_url('admin.php?page=ph-settings&tab=offices');
330 363
331 364 PH_Admin_Settings::output_fields( $settings );
332 365
@@ -333,12 +366,14 @@
333 366 } elseif ( $current_section == 'edit' ) {
334 367
335 368 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
336 369
337 - $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;
338 372
339 373 $settings = $this->get_office_settings();
340 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.
341 376 $redirect_after_save = admin_url('admin.php?page=ph-settings&tab=offices');
342 377
343 378 PH_Admin_Settings::output_fields( $settings );
344 379
@@ -345,9 +380,10 @@
345 380 } elseif ( $current_section == 'delete' ) {
346 381
347 382 remove_action('propertyhive_admin_field_offices', array( $this, 'offices_setting' ));
348 383
349 - $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;
350 386
351 387 $settings = $this->get_office_delete();
352 388
353 389 PH_Admin_Settings::output_fields( $settings );
@@ -444,9 +480,9 @@
444 480 <td class="address">
445 481 ' . esc_html($address) . '
446 482 </td>
447 483 <td class="contact">
448 - ' . $contact_details . '
484 + ' . wp_kses_post( $contact_details ) . '
449 485 </td>';
450 486 do_action( 'propertyhive_office_table_row_columns', get_the_ID() );
451 487 echo '
452 488 <td class="settings">
@@ -481,10 +517,22 @@
481 517 /**
482 518 * Save settings
483 519 */
484 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 +
485 525 global $current_section, $post;
486 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 +
487 535 if ( $current_section == 'add' ) {
488 536
489 537 // TODO: Validate (check for blank fields, and that office name doest exist already)
490 538
@@ -489,9 +537,9 @@
489 537 // TODO: Validate (check for blank fields, and that office name doest exist already)
490 538
491 539 // Insert office
492 540 $office_post = array(
493 - 'post_title' => ph_clean( $_POST['office_name'] ),
541 + 'post_title' => wp_slash( $this->get_posted_text( 'office_name' ) ),
494 542 'post_content' => '',
495 543 'post_status' => 'publish',
496 544 'post_type' => 'office',
497 545 'comment_status' => 'closed',
@@ -507,18 +555,17 @@
507 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>' );
508 556
509 557 } elseif ( $current_section == 'edit' ) {
510 558
511 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
559 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
512 560
513 561 // TODO: Validate
514 - // TODO: Make sure this ID belongs to an office
515 562 // TODO: Update slug?
516 563
517 564 // Update office
518 565 $office_post = array(
519 566 'ID' => $current_id,
520 - 'post_title' => ph_clean( $_POST['office_name'] )
567 + 'post_title' => wp_slash( $this->get_posted_text( 'office_name' ) )
521 568 );
522 569
523 570 wp_update_post( $office_post );
524 571
@@ -530,11 +577,11 @@
530 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>' );
531 578
532 579 } elseif ( $current_section == 'delete' ) {
533 580
534 - if ( isset($_POST['confirm_removal']) && $_POST['confirm_removal'] == '1' )
581 + if ( '1' === $this->get_posted_text( 'confirm_removal' ) )
535 582 {
536 - $current_id = empty( $_REQUEST['id'] ) ? '' : (int)$_REQUEST['id'];
583 + $current_id = isset( $_REQUEST['id'] ) && is_scalar( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
537 584
538 585 // Get number of properties assigned to this term
539 586 $query_args = array(
540 587 'post_type' => 'property',
@@ -539,8 +586,9 @@
539 586 $query_args = array(
540 587 'post_type' => 'property',
541 588 'nopaging' => true,
542 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.
543 591 'meta_query' => array(
544 592 array(
545 593 'key' => '_office_id',
546 594 'value' => $current_id,
@@ -557,9 +605,10 @@
557 605 die("Not assigning properties to new office. Please try again");
558 606 }
559 607 else
560 608 {
561 - $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 );
562 611
563 612 if ( $post_type != 'office' )
564 613 {
565 614 die("New office isn't of type office. It's of type: " . esc_html($post_type));
@@ -569,9 +618,9 @@
569 618 while ( $property_query->have_posts() )
570 619 {
571 620 $property_query->the_post();
572 621
573 - update_post_meta( $post->ID, '_office_id', (int)$_POST['reassign_to'] );
622 + update_post_meta( $post->ID, '_office_id', $reassign_to );
574 623
575 624 // TODO: Check for WP_ERROR
576 625 }
577 626 }
@@ -610,17 +659,17 @@
610 659
611 660 wp_reset_postdata();
612 661
613 662 // Set selected office as primary
614 - update_post_meta( (int)$_POST['primary'], 'primary', '1');
663 + update_post_meta( absint( $this->get_posted_text( 'primary' ) ), 'primary', '1');
615 664 }
616 665 else
617 666 {
618 - update_post_meta($office_post_id, '_office_address_1', ph_clean( $_POST['_office_address_1'] ));
619 - update_post_meta($office_post_id, '_office_address_2', ph_clean( $_POST['_office_address_2'] ));
620 - update_post_meta($office_post_id, '_office_address_3', ph_clean( $_POST['_office_address_3'] ));
621 - update_post_meta($office_post_id, '_office_address_4', ph_clean( $_POST['_office_address_4'] ));
622 - 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' ) ));
623 672
624 673 $departments = ph_get_departments();
625 674
626 675 foreach ( $departments as $key => $value )
@@ -626,15 +675,17 @@
626 675 foreach ( $departments as $key => $value )
627 676 {
628 677 if ( get_option( 'propertyhive_active_departments_' . str_replace("residential-", "", $key) ) == 'yes' )
629 678 {
630 - 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)] ) : '');
631 - 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 ) );
632 683 }
633 684 }
634 685
635 - update_post_meta($office_post_id, '_office_latitude', ph_clean( $_POST['_office_latitude'] ));
636 - 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' ) ));
637 688
638 689 do_action( 'propertyhive_save_office', $office_post_id );
639 690 }
640 691 }