| @@ -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 | * Setup menus in WP admin. |
| 4 | 7 | * |
| 5 | 8 | * @author PropertyHive |
| @@ -14,8 +17,9 @@ | ||
| 14 | 17 | |
| 15 | 18 | /** |
| 16 | 19 | * PH_Admin_Menus Class |
| 17 | 20 | */ |
| 21 | +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Admin_Menus; preserving the existing PH_* class name is required for plugin and extension compatibility. | |
| 18 | 22 | class PH_Admin_Menus { |
| 19 | 23 | |
| 20 | 24 | /** |
| 21 | 25 | * Hook in tabs. |
| @@ -47,19 +51,26 @@ | ||
| 47 | 51 | // Include settings pages. |
| 48 | 52 | PH_Admin_Settings::get_settings_pages(); |
| 49 | 53 | |
| 50 | 54 | // Get current tab/section. |
| 51 | - $current_tab = empty( $_GET['tab'] ) ? 'general' : sanitize_title( wp_unslash( $_GET['tab'] ) ); // WPCS: input var okay, CSRF ok. | |
| 52 | - $current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( wp_unslash( $_REQUEST['section'] ) ); // WPCS: input var okay, CSRF ok. | |
| 55 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound, WordPress.Security.NonceVerification.Recommended -- Read-only settings routing; saved data is separately nonce/capability checked by PH_Admin_Settings::save(). Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global. | |
| 56 | + $current_tab = empty( $_GET['tab'] ) || ! is_string( $_GET['tab'] ) ? 'general' : sanitize_title( wp_unslash( $_GET['tab'] ) ); // WPCS: input var okay, CSRF ok. | |
| 57 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound, WordPress.Security.NonceVerification.Recommended -- Read-only settings routing; saved data is separately nonce/capability checked by PH_Admin_Settings::save(). Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global. | |
| 58 | + $current_section = empty( $_REQUEST['section'] ) || ! is_string( $_REQUEST['section'] ) ? '' : sanitize_title( wp_unslash( $_REQUEST['section'] ) ); // WPCS: input var okay, CSRF ok. | |
| 53 | 59 | |
| 54 | - // Save settings if data has been posted. | |
| 55 | - if ( '' !== $current_section && apply_filters( "propertyhive_save_settings_{$current_tab}_{$current_section}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok. | |
| 60 | + // Save settings if data has been posted. The called save method verifies nonce and capabilities. | |
| 61 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only save intent; PH_Admin_Settings::save() validates before changing any settings. | |
| 62 | + $save_requested = ! empty( $_POST['save'] ); | |
| 63 | + if ( '' !== $current_section && apply_filters( "propertyhive_save_settings_{$current_tab}_{$current_section}", $save_requested ) ) { // WPCS: input var okay, CSRF ok. | |
| 56 | 64 | PH_Admin_Settings::save(); |
| 57 | - } elseif ( '' === $current_section && apply_filters( "propertyhive_save_settings_{$current_tab}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok. | |
| 65 | + } elseif ( '' === $current_section && apply_filters( "propertyhive_save_settings_{$current_tab}", $save_requested ) ) { // WPCS: input var okay, CSRF ok. | |
| 58 | 66 | PH_Admin_Settings::save(); |
| 59 | 67 | } |
| 60 | 68 | |
| 61 | - $redirect_after_save = empty( $_POST['redirect'] ) ? '' : sanitize_url( wp_unslash( $_POST['redirect'] ) ); | |
| 69 | + $redirect_after_save = ''; | |
| 70 | + 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' ) ) { | |
| 71 | + $redirect_after_save = isset( $_POST['redirect'] ) && is_string( $_POST['redirect'] ) ? sanitize_url( wp_unslash( $_POST['redirect'] ) ) : ''; | |
| 72 | + } | |
| 62 | 73 | if ( !empty($redirect_after_save) ) |
| 63 | 74 | { |
| 64 | 75 | wp_safe_redirect($redirect_after_save . '&ph_message=' . __( 'Your settings have been saved.', 'propertyhive' ) ); |
| 65 | 76 | die(); |
| @@ -196,11 +207,13 @@ | ||
| 196 | 207 | if ( apply_filters( 'propertyhive_show_admin_menu_enquiry_count', TRUE ) === TRUE ) |
| 197 | 208 | { |
| 198 | 209 | $args = array( |
| 199 | 210 | 'post_type' => 'enquiry', |
| 200 | - 'nopaging' => true, | |
| 211 | + 'posts_per_page' => 1, | |
| 212 | + 'no_found_rows' => false, | |
| 201 | 213 | 'fields' => 'ids', |
| 202 | - 'meta_query' => array( | |
| 214 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Count-only menu query fetches one ID while retaining found_rows for the complete total. | |
| 215 | + 'meta_query' => array( | |
| 203 | 216 | array( |
| 204 | 217 | 'key' => '_status', |
| 205 | 218 | 'value' => 'open' |
| 206 | 219 | ), |
| @@ -244,11 +257,13 @@ | ||
| 244 | 257 | if ( apply_filters( 'propertyhive_show_admin_menu_key_date_count', TRUE ) === TRUE ) |
| 245 | 258 | { |
| 246 | 259 | $args = array( |
| 247 | 260 | 'post_type' => 'key_date', |
| 248 | - 'nopaging' => true, | |
| 261 | + 'posts_per_page' => 1, | |
| 262 | + 'no_found_rows' => false, | |
| 249 | 263 | 'fields' => 'ids', |
| 250 | - 'meta_query' => array( | |
| 264 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Count-only menu query fetches one ID while retaining found_rows for the complete total. | |
| 265 | + 'meta_query' => array( | |
| 251 | 266 | array( |
| 252 | 267 | 'key' => '_key_date_status', |
| 253 | 268 | 'value' => 'pending' |
| 254 | 269 | ), |
| @@ -253,9 +268,9 @@ | ||
| 253 | 268 | 'value' => 'pending' |
| 254 | 269 | ), |
| 255 | 270 | array( |
| 256 | 271 | 'key' => '_date_due', |
| 257 | - 'value' => date('Y-m-d'), | |
| 272 | + 'value' => gmdate('Y-m-d'), | |
| 258 | 273 | 'type' => 'date', |
| 259 | 274 | 'compare' => '<=', |
| 260 | 275 | ), |
| 261 | 276 | ), |
| @@ -409,11 +424,13 @@ | ||
| 409 | 424 | $crm_only_mode = get_user_meta( $user_id, 'crm_only_mode', TRUE ); |
| 410 | 425 | |
| 411 | 426 | if ( $crm_only_mode == '1' ) |
| 412 | 427 | { |
| 413 | - if ( $post_type == 'contact' && isset($_GET['_contact_type']) && !empty(ph_clean($_GET['_contact_type'])) ) | |
| 428 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only CRM navigation highlighting, not a data mutation. | |
| 429 | + $contact_type = isset( $_GET['_contact_type'] ) && is_string( $_GET['_contact_type'] ) ? sanitize_text_field( wp_unslash( $_GET['_contact_type'] ) ) : ''; | |
| 430 | + if ( $post_type == 'contact' && '' !== $contact_type ) | |
| 414 | 431 | { |
| 415 | - $parent_file = 'edit.php?post_type=contact&_contact_type=' . ph_clean($_GET['_contact_type']); | |
| 432 | + $parent_file = 'edit.php?post_type=contact&_contact_type=' . rawurlencode( $contact_type ); | |
| 416 | 433 | } |
| 417 | 434 | } |
| 418 | 435 | else |
| 419 | 436 | { |