init_hooks(); } /** * Initialize WordPress hooks. * * @since 0.1.0 */ private function init_hooks(): void { // Priority 20 ensures our page registers after Two Factor (default priority 10). add_action( 'admin_menu', array( $this, 'register_settings_page' ), 20 ); add_action( 'network_admin_menu', array( $this, 'register_network_settings_page' ), 20 ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_init', array( $this, 'handle_import_export' ) ); add_action( 'admin_init', array( $this, 'handle_reset_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) ); } /** * Register settings page in admin menu. * * @since 0.1.0 */ public function register_settings_page(): void { // Check capability. if ( ! current_user_can( 'manage_options' ) ) { return; } // Main settings page with tabs (Settings, Audit Log, Compliance). add_options_page( __( 'Two Factor Extended', 'two-factor-extended' ), __( 'Two Factor Extended', 'two-factor-extended' ), 'manage_options', self::PAGE_SLUG, array( $this, 'render_settings_page' ) ); } /** * Register network settings page for Multisite. * * @since 0.1.0 */ public function register_network_settings_page(): void { // Only for Multisite. if ( ! is_multisite() ) { return; } // Check capability. if ( ! current_user_can( 'manage_network_options' ) ) { return; } add_submenu_page( 'settings.php', __( 'Two Factor Extended', 'two-factor-extended' ), __( 'Two Factor Extended', 'two-factor-extended' ), 'manage_network_options', self::PAGE_SLUG, array( $this, 'render_network_settings_page' ) ); } /** * Register settings sections and fields. * * @since 0.1.0 */ public function register_settings(): void { // Register main settings. register_setting( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array( 'type' => 'array', 'sanitize_callback' => array( $this, 'sanitize_settings' ), 'default' => $this->get_default_settings(), ) ); // Register data removal option. register_setting( TWO_FACTOR_EXTENDED_OPTION_REMOVE_DATA, TWO_FACTOR_EXTENDED_OPTION_REMOVE_DATA, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => false, ) ); // Register sections. $this->register_sections(); // Register fields. $this->register_fields(); } /** * Register settings sections. * * @since 0.1.0 */ private function register_sections(): void { // General settings section. add_settings_section( 'two_factor_extended_general', __( 'General Settings', 'two-factor-extended' ), array( $this, 'render_general_section' ), self::PAGE_SLUG ); // Role requirements section. add_settings_section( 'two_factor_extended_role_requirements', __( 'Role-Based 2FA Requirements', 'two-factor-extended' ), array( $this, 'render_role_requirements_section' ), self::PAGE_SLUG ); // Provider visibility section. add_settings_section( 'two_factor_extended_provider_visibility', __( 'Role-Based Provider Visibility', 'two-factor-extended' ), array( $this, 'render_provider_visibility_section' ), self::PAGE_SLUG ); // Data management section. add_settings_section( 'two_factor_extended_data', __( 'Data Management', 'two-factor-extended' ), array( $this, 'render_data_section' ), self::PAGE_SLUG ); } /** * Register settings fields. * * @since 0.1.0 */ private function register_fields(): void { // Grace period field. add_settings_field( 'grace_period_days', __( 'Grace Period (Days)', 'two-factor-extended' ), array( $this, 'render_number_field' ), self::PAGE_SLUG, 'two_factor_extended_general', array( 'option_name' => TWO_FACTOR_EXTENDED_OPTION_SETTINGS, 'id' => 'grace_period_days', 'field_key' => 'grace_period_days', 'description' => __( 'Number of days users have to configure required 2FA methods before enforcement. Set to 0 for immediate enforcement.', 'two-factor-extended' ), 'min' => 0, 'max' => 365, 'default' => 7, ) ); // Role requirements field. add_settings_field( 'role_requirements', __( 'Role Requirements', 'two-factor-extended' ), array( $this, 'render_role_requirements_field' ), self::PAGE_SLUG, 'two_factor_extended_role_requirements', array() ); // Provider visibility field. add_settings_field( 'provider_visibility', __( 'Provider Visibility', 'two-factor-extended' ), array( $this, 'render_provider_visibility_field' ), self::PAGE_SLUG, 'two_factor_extended_provider_visibility', array() ); // Data removal field. add_settings_field( 'remove_data_on_uninstall', __( 'Remove Data on Uninstall', 'two-factor-extended' ), array( $this, 'render_checkbox_field' ), self::PAGE_SLUG, 'two_factor_extended_data', array( 'option_name' => TWO_FACTOR_EXTENDED_OPTION_REMOVE_DATA, 'id' => 'remove_data_on_uninstall', 'label' => __( 'Delete all plugin data when the plugin is uninstalled', 'two-factor-extended' ), 'description' => __( 'Warning: This action cannot be undone. All plugin settings and data will be permanently deleted.', 'two-factor-extended' ), ) ); } /** * Render general settings section. * * @since 0.1.0 */ public function render_general_section(): void { ?>

render_network_inheritance_notice(); } /** * Render role requirements section. * * @since 0.1.0 */ public function render_role_requirements_section(): void { ?>

$args Field arguments. */ public function render_number_field( array $args ): void { $option_name = isset( $args['option_name'] ) && is_string( $args['option_name'] ) ? $args['option_name'] : ''; $id = isset( $args['id'] ) && is_string( $args['id'] ) ? $args['id'] : ''; $field_key = isset( $args['field_key'] ) && is_string( $args['field_key'] ) ? $args['field_key'] : ''; $description = isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : ''; $min = isset( $args['min'] ) && is_numeric( $args['min'] ) ? (int) $args['min'] : 0; $max = isset( $args['max'] ) && is_numeric( $args['max'] ) ? (int) $args['max'] : 999; $default = isset( $args['default'] ) && is_numeric( $args['default'] ) ? (int) $args['default'] : 0; if ( empty( $option_name ) || empty( $id ) ) { return; } $settings = get_option( $option_name, array() ); $value = is_array( $settings ) && isset( $settings[ $field_key ] ) && is_numeric( $settings[ $field_key ] ) ? (int) $settings[ $field_key ] : $default; ?>

$args Field arguments. */ public function render_role_requirements_field( array $args ): void { // Clear any object cache to ensure fresh data. wp_cache_delete( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, 'options' ); wp_cache_flush(); $raw_settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() ); $settings = is_array( $raw_settings ) ? $raw_settings : array(); $roles = Two_Factor_Extended_Role_Manager::get_all_roles(); // Force fresh provider detection directly from Two Factor Core. $providers = array(); if ( class_exists( 'Two_Factor_Core' ) ) { $all_providers = Two_Factor_Core::get_providers(); foreach ( $all_providers as $class_name => $provider ) { if ( is_object( $provider ) && method_exists( $provider, 'get_label' ) ) { $providers[ $class_name ] = $provider->get_label(); } else { $providers[ $class_name ] = $class_name; } } } if ( empty( $providers ) ) { ?>

$role_name ) : ?>
$provider_name ) : $checked = in_array( $provider_class, $role_requirements, true ); ?>

$args Field arguments. */ public function render_provider_visibility_field( array $args ): void { // Clear any object cache to ensure fresh data. wp_cache_delete( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, 'options' ); wp_cache_flush(); $raw_settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() ); $settings = is_array( $raw_settings ) ? $raw_settings : array(); $roles = Two_Factor_Extended_Role_Manager::get_all_roles(); // Force fresh provider detection directly from Two Factor Core. $providers = array(); if ( class_exists( 'Two_Factor_Core' ) ) { $all_providers = Two_Factor_Core::get_providers(); foreach ( $all_providers as $class_name => $provider ) { if ( is_object( $provider ) && method_exists( $provider, 'get_label' ) ) { $providers[ $class_name ] = $provider->get_label(); } else { $providers[ $class_name ] = $class_name; } } } if ( empty( $providers ) ) { ?>

$role_name ) : ?>
$provider_name ) : $checked = in_array( $provider_class, $role_visibility, true ); $is_required = in_array( $provider_class, $role_required, true ); $disabled = $is_required ? 'disabled' : ''; $checked = $checked || $is_required; ?>

$args Field arguments. */ public function render_checkbox_field( array $args ): void { $option_name = isset( $args['option_name'] ) && is_string( $args['option_name'] ) ? $args['option_name'] : ''; $id = isset( $args['id'] ) && is_string( $args['id'] ) ? $args['id'] : ''; $label = isset( $args['label'] ) && is_string( $args['label'] ) ? $args['label'] : ''; $description = isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : ''; if ( empty( $option_name ) || empty( $id ) ) { return; } $value = get_option( $option_name, false ); ?>

maxSize) { e.preventDefault(); alert('" . esc_js( __( 'File size exceeds 1MB limit. Please use a smaller file.', 'two-factor-extended' ) ) . "'); return false; } // Check file extension. if (!file.name.endsWith('.json')) { e.preventDefault(); alert('" . esc_js( __( 'Please select a valid JSON file (.json extension).', 'two-factor-extended' ) ) . "'); return false; } // Confirm before proceeding. if (!confirm('" . esc_js( __( 'Importing will overwrite your current settings. Are you sure you want to continue?', 'two-factor-extended' ) ) . "')) { e.preventDefault(); return false; } }); } })(); "; wp_add_inline_script( 'jquery', $script ); } /** * Handle import/export actions. * * @since 0.1.0 */ public function handle_import_export(): void { // Check if export action. if ( isset( $_POST['two_factor_extended_export'] ) ) { // Verify nonce and capability. check_admin_referer( 'two_factor_extended_export', 'two_factor_extended_export_nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to perform this action.', 'two-factor-extended' ) ); } $this->export_settings(); } // Check if import action. if ( isset( $_POST['two_factor_extended_import'] ) && isset( $_FILES['two_factor_extended_import_file'] ) ) { // Verify nonce and capability. check_admin_referer( 'two_factor_extended_import', 'two_factor_extended_import_nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to perform this action.', 'two-factor-extended' ) ); } $this->import_settings(); } } /** * Export settings to JSON file. * * @since 0.1.0 * * @throws Exception If JSON encoding fails. */ private function export_settings(): void { try { $settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() ); $export_data = array( 'version' => TWO_FACTOR_EXTENDED_VERSION, 'timestamp' => current_time( 'timestamp' ), 'settings' => $settings, ); // Encode JSON. $json = wp_json_encode( $export_data, JSON_PRETTY_PRINT ); if ( false === $json ) { throw new Exception( 'Failed to encode settings to JSON' ); } // Log the export. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'settings_exported', 'Plugin settings exported', 0 ); } // Set headers for download. header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=two-factor-extended-settings-' . gmdate( 'Y-m-d-H-i-s' ) . '.json' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); echo wp_json_encode( json_decode( $json ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Already JSON encoded exit; } catch ( Exception $e ) { // Log error without exposing details to user. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'settings_export_failed', 'Failed to export settings', 0, array( 'error' => 'Export failed' ) ); } wp_die( esc_html__( 'Unable to export settings. Please try again later.', 'two-factor-extended' ), esc_html__( 'Export Error', 'two-factor-extended' ), array( 'response' => 500 ) ); } } /** * Import settings from JSON file. * * Note: Nonce verification is performed in handle_import_export() at line 764 * via check_admin_referer() before calling this method. This is a private * method that should only be called after nonce and capability checks. * * @since 0.1.0 */ private function import_settings(): void { /* * Disable PHPCS warnings for $_FILES access in this method. * Justification: * - Nonce is verified in handle_import_export() line 764 via check_admin_referer() * - This is a private method only called after nonce and capability verification * - All file data is validated below (size, extension, content) * - File name is sanitized with sanitize_file_name() * - Content is decoded and validated as JSON before use */ // phpcs:disable WordPress.Security.NonceVerification.Missing // phpcs:disable WordPress.Security.ValidatedSanitizedInput // Check file upload. $upload_file = isset( $_FILES['two_factor_extended_import_file'] ) && is_array( $_FILES['two_factor_extended_import_file'] ) ? $_FILES['two_factor_extended_import_file'] : array(); $upload_error = isset( $upload_file['error'] ) && is_int( $upload_file['error'] ) ? $upload_file['error'] : UPLOAD_ERR_NO_FILE; $upload_size = isset( $upload_file['size'] ) && is_int( $upload_file['size'] ) ? $upload_file['size'] : 0; $upload_name = isset( $upload_file['name'] ) && is_string( $upload_file['name'] ) ? $upload_file['name'] : ''; $upload_tmp_name = isset( $upload_file['tmp_name'] ) && is_string( $upload_file['tmp_name'] ) ? $upload_file['tmp_name'] : ''; if ( empty( $upload_file ) || UPLOAD_ERR_OK !== $upload_error ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'Error uploading file. Please try again.', 'two-factor-extended' ), 'error' ); return; } // Check file size (1MB limit). $max_size = 1024 * 1024; // 1MB. if ( $upload_size > $max_size ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'File size exceeds 1MB limit. Please use a smaller file.', 'two-factor-extended' ), 'error' ); return; } // Check file extension. $file_name = sanitize_file_name( $upload_name ); if ( ! str_ends_with( strtolower( $file_name ), '.json' ) ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'Invalid file type. Please upload a JSON file.', 'two-factor-extended' ), 'error' ); return; } // Read file contents. $file_content = file_get_contents( $upload_tmp_name ); // phpcs:enable WordPress.Security.NonceVerification.Missing // phpcs:enable WordPress.Security.ValidatedSanitizedInput if ( false === $file_content ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'Error reading file. Please try again.', 'two-factor-extended' ), 'error' ); return; } // Decode JSON. $import_data = json_decode( $file_content, true ); if ( null === $import_data || ! is_array( $import_data ) ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'Invalid JSON file. Please upload a valid settings export file.', 'two-factor-extended' ), 'error' ); return; } // Validate import data structure. if ( ! isset( $import_data['settings'] ) || ! is_array( $import_data['settings'] ) ) { add_settings_error( 'two_factor_extended_import', 'import_error', __( 'Invalid settings file structure.', 'two-factor-extended' ), 'error' ); return; } // Sanitize and update settings. Ensure keys are strings (from JSON decode). $settings_to_import = array(); foreach ( $import_data['settings'] as $key => $value ) { if ( is_string( $key ) ) { $settings_to_import[ $key ] = $value; } } $sanitized_settings = $this->sanitize_settings( $settings_to_import ); update_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, $sanitized_settings ); // Log the import. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'settings_imported', 'Plugin settings imported', 0, array( 'imported_version' => $import_data['version'] ?? 'unknown', ) ); } add_settings_error( 'two_factor_extended_import', 'import_success', __( 'Settings imported successfully!', 'two-factor-extended' ), 'success' ); } /** * Render settings page. * * @since 0.1.0 */ public function render_settings_page(): void { // Check capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'two-factor-extended' ) ); } // Get current tab. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab parameter for UI navigation $tab_param = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? $_GET['tab'] : ''; $current_tab = $tab_param ? sanitize_key( $tab_param ) : 'settings'; // Define tabs. $tabs = array( 'settings' => __( 'Settings', 'two-factor-extended' ), 'audit-log' => __( 'Audit Log', 'two-factor-extended' ), 'compliance' => __( 'Compliance', 'two-factor-extended' ), ); ?>

render_audit_log_tab(); break; case 'compliance': $this->render_compliance_tab(); break; case 'settings': default: $this->render_settings_tab(); break; } ?>
are_site_settings_disabled(); if ( $settings_disabled ) : ?>

get_audit_log(); if ( null === $audit_log ) { echo '

' . esc_html__( 'Audit log module is not available.', 'two-factor-extended' ) . '

'; return; } // Handle export. if ( isset( $_GET['action'] ) && 'export' === $_GET['action'] ) { check_admin_referer( 'two_factor_extended_export_audit' ); $csv = $audit_log->export_to_csv(); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="audit-log-' . gmdate( 'Y-m-d' ) . '.csv"' ); echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } // Handle clear logs. if ( isset( $_POST['action'] ) && 'clear' === $_POST['action'] ) { check_admin_referer( 'two_factor_extended_clear_audit' ); $audit_log->clear_logs(); echo '

' . esc_html__( 'Audit logs cleared successfully.', 'two-factor-extended' ) . '

'; } $logs = $audit_log->get_logs(); $stats = $audit_log->get_statistics(); ?>

display_name ) : esc_html__( 'Unknown', 'two-factor-extended' ); } else { esc_html_e( 'System', 'two-factor-extended' ); } ?>
50 ) : ?>

get_compliance_report(); if ( null === $compliance ) { echo '

' . esc_html__( 'Compliance report module is not available.', 'two-factor-extended' ) . '

'; return; } // Handle export. if ( isset( $_GET['action'] ) && 'export' === $_GET['action'] ) { check_admin_referer( 'two_factor_extended_export_compliance' ); $csv = $compliance->export_to_csv(); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="compliance-report-' . gmdate( 'Y-m-d' ) . '.csv"' ); echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } $stats = $compliance->get_compliance_stats(); $non_compliant = $compliance->get_non_compliant_users(); ?>

$role_stats ) : ?>

display_name ) : esc_html( $user_login_val ); ?>
get_audit_log(); if ( null === $audit_log ) { wp_die( esc_html__( 'Audit log module is not available.', 'two-factor-extended' ) ); } // Handle export. if ( isset( $_GET['action'] ) && 'export' === $_GET['action'] ) { check_admin_referer( 'two_factor_extended_export_audit' ); $csv = $audit_log->export_to_csv(); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="audit-log-' . gmdate( 'Y-m-d' ) . '.csv"' ); echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } // Handle clear logs. if ( isset( $_POST['action'] ) && 'clear' === $_POST['action'] ) { check_admin_referer( 'two_factor_extended_clear_audit' ); $audit_log->clear_logs(); echo '

' . esc_html__( 'Audit logs cleared successfully.', 'two-factor-extended' ) . '

'; } $logs = $audit_log->get_logs(); $stats = $audit_log->get_statistics(); ?>

user_login ) : '-'; ?>
50 ) : ?>

get_compliance_report(); if ( null === $compliance ) { wp_die( esc_html__( 'Compliance report module is not available.', 'two-factor-extended' ) ); } // Handle export. if ( isset( $_GET['action'] ) && 'export' === $_GET['action'] ) { check_admin_referer( 'two_factor_extended_export_compliance' ); $csv = $compliance->export_to_csv(); header( 'Content-Type: text/csv' ); header( 'Content-Disposition: attachment; filename="compliance-report-' . gmdate( 'Y-m-d' ) . '.csv"' ); echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } // Handle email report. if ( isset( $_POST['action'] ) && 'email' === $_POST['action'] ) { check_admin_referer( 'two_factor_extended_email_compliance' ); $email = isset( $_POST['email'] ) && is_string( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; if ( ! empty( $email ) ) { $sent = $compliance->email_report( $email ); if ( $sent ) { echo '

' . esc_html__( 'Report sent successfully.', 'two-factor-extended' ) . '

'; } else { echo '

' . esc_html__( 'Failed to send report.', 'two-factor-extended' ) . '

'; } } } $stats = $compliance->get_compliance_stats(); $non_compliant = $compliance->get_non_compliant_users(); ?>

$role_stats ) : ?>

save_network_settings(); // Redirect with success message. wp_safe_redirect( add_query_arg( 'updated', 'true', network_admin_url( 'settings.php?page=' . self::PAGE_SLUG ) ) ); exit; } ?>

Default settings. */ private function get_default_settings(): array { $all_roles = Two_Factor_Extended_Role_Manager::get_all_roles(); $defaults = array( 'enabled' => false, 'grace_period_days' => 7, 'role_requirements' => array(), 'provider_visibility' => array(), ); // Initialize all roles with empty arrays. foreach ( array_keys( $all_roles ) as $role_slug ) { $defaults['role_requirements'][ $role_slug ] = array(); $defaults['provider_visibility'][ $role_slug ] = array(); } return $defaults; } /** * Get plugin settings. * * @since 0.1.0 * * @return array Plugin settings. */ public function get_settings(): array { $settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() ); if ( ! is_array( $settings ) ) { $settings = array(); } return wp_parse_args( $settings, $this->get_default_settings() ); } /** * Update plugin settings. * * @since 0.1.0 * * @param array $settings New settings values. * * @return bool True if settings were updated successfully. */ public function update_settings( array $settings ): bool { $sanitized = $this->sanitize_settings( $settings ); return update_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, $sanitized ); } /** * Sanitize settings. * * @since 0.1.0 * * @param array $input Raw settings input. * * @return array Sanitized settings. */ public function sanitize_settings( array $input ): array { $sanitized = array(); // Sanitize enabled setting. if ( isset( $input['enabled'] ) ) { $sanitized['enabled'] = (bool) $input['enabled']; } // Sanitize grace period days. if ( isset( $input['grace_period_days'] ) && is_numeric( $input['grace_period_days'] ) ) { $grace_days = (int) $input['grace_period_days']; $sanitized['grace_period_days'] = max( 0, min( 365, $grace_days ) ); } // Get all roles once for both role requirements and provider visibility. $all_roles = Two_Factor_Extended_Role_Manager::get_all_roles(); // Sanitize role requirements. // Initialize all roles with empty arrays to preserve structure when all checkboxes are unchecked. $sanitized['role_requirements'] = array(); foreach ( array_keys( $all_roles ) as $role_slug ) { $sanitized['role_requirements'][ $role_slug ] = array(); } // Now merge any submitted role requirements data. if ( isset( $input['role_requirements'] ) && is_array( $input['role_requirements'] ) ) { foreach ( $input['role_requirements'] as $role => $providers ) { if ( ! is_string( $role ) ) { continue; } $role_slug = sanitize_key( $role ); if ( ! Two_Factor_Extended_Role_Manager::role_exists( $role_slug ) ) { continue; } if ( is_array( $providers ) ) { $sanitized['role_requirements'][ $role_slug ] = array_map( function ( $item ) { return sanitize_text_field( is_scalar( $item ) ? (string) $item : '' ); }, $providers ); } } } // Sanitize provider visibility. // Initialize all roles with empty arrays to preserve structure when all checkboxes are unchecked. $sanitized['provider_visibility'] = array(); foreach ( array_keys( $all_roles ) as $role_slug ) { $sanitized['provider_visibility'][ $role_slug ] = array(); } // Now merge any submitted provider visibility data. if ( isset( $input['provider_visibility'] ) && is_array( $input['provider_visibility'] ) ) { foreach ( $input['provider_visibility'] as $role => $providers ) { if ( ! is_string( $role ) ) { continue; } $role_slug = sanitize_key( $role ); if ( ! Two_Factor_Extended_Role_Manager::role_exists( $role_slug ) ) { continue; } if ( is_array( $providers ) ) { // Remove duplicates and sanitize. $sanitized['provider_visibility'][ $role_slug ] = array_unique( array_map( function ( $item ) { return sanitize_text_field( is_scalar( $item ) ? (string) $item : '' ); }, $providers ) ); } } } // Clear object cache to prevent stale data. wp_cache_delete( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, 'options' ); return $sanitized; } /** * Validate settings input. * * @since 0.1.0 * * @param array $input Settings input to validate. * * @return bool True if valid, false otherwise. */ public function validate_settings( array $input ): bool { // Basic validation - can be extended. // Since parameter is typed as array, it's always valid. return true; } /** * Handle reset settings action. * * @since 1.0.0 */ public function handle_reset_settings(): void { // Check if reset was requested. if ( ! isset( $_POST['two_factor_extended_reset'] ) ) { return; } // Verify nonce. if ( ! isset( $_POST['two_factor_extended_reset_nonce'] ) || ! is_string( $_POST['two_factor_extended_reset_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['two_factor_extended_reset_nonce'] ) ), 'two_factor_extended_reset' ) ) { wp_die( esc_html__( 'Security check failed.', 'two-factor-extended' ) ); } // Check capability. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to perform this action.', 'two-factor-extended' ) ); } try { // Get default settings. $default_settings = $this->get_default_settings(); // Reset plugin settings. update_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, $default_settings ); // Clear user grace period data. $users = get_users( array( 'fields' => 'ID' ) ); foreach ( $users as $user_id ) { delete_user_meta( (int) $user_id, 'two_factor_extended_enforcement_date' ); } // Log the reset action. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'settings_reset', 'Plugin settings reset to default values' ); } // Redirect with success message. add_settings_error( 'two_factor_extended_messages', 'two_factor_extended_reset', __( 'Plugin settings have been reset to default values successfully.', 'two-factor-extended' ), 'success' ); set_transient( 'settings_errors', get_settings_errors(), 30 ); $redirect_url = add_query_arg( array( 'page' => self::PAGE_SLUG, 'settings-updated' => 'true', ), admin_url( 'options-general.php' ) ); wp_safe_redirect( $redirect_url ); exit; } catch ( Exception $e ) { // Handle error gracefully. add_settings_error( 'two_factor_extended_messages', 'two_factor_extended_reset_error', __( 'Failed to reset plugin settings. Please try again.', 'two-factor-extended' ), 'error' ); } } }