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 ) ) {
?>
$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 ) ) {
?>
$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' ),
);
?>
$tab_label ) : ?>
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();
?>
|
|
|
|
|
| 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();
?>
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();
?>
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'
);
}
}
}