Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions settings/class-two-factor-settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php
class Two_Factor_Settings {

public static function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}

// Handle save.
if ( isset( $_POST['two_factor_settings_submit'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to perform this action.', 'two-factor' ) );
}
check_admin_referer( 'two_factor_save_settings', 'two_factor_settings_nonce' );

$posted = isset( $_POST['two_factor_disabled_providers'] ) && is_array( $_POST['two_factor_disabled_providers'] ) ? wp_unslash( $_POST['two_factor_disabled_providers'] ) : array();

// Sanitize posted values immediately.
$posted = array_map( 'sanitize_text_field', (array) $posted );
// Remove empty values.
$disabled = array_values( array_filter( $posted, 'strlen' ) );

if ( ! empty( $disabled ) ) {
update_option( 'two_factor_disabled_providers', array_values( array_unique( $disabled ) ) );
} else {
// Empty means none disabled (all allowed).
delete_option( 'two_factor_disabled_providers' );
}

echo '<div class="updated"><p>' . esc_html__( 'Settings saved.', 'two-factor' ) . '</p></div>';
}

// Build provider list for display using reflection (safe for private methods).
$default_providers = self::get_core_default_providers();
$all_providers = apply_filters( 'two_factor_providers', $default_providers );

$provider_instances = array();
foreach ( $all_providers as $provider_key => $provider_path ) {
if ( ! empty( $provider_path ) && is_readable( $provider_path ) ) {
require_once $provider_path;
}

$class = $provider_key;
/** This filter mirrors core behavior for dynamic classname filters. */
$class = apply_filters( "two_factor_provider_classname_{$provider_key}", $class, $provider_path );

if ( class_exists( $class ) ) {
try {
$provider_instances[ $provider_key ] = call_user_func( array( $class, 'get_instance' ) );
} catch ( Exception $e ) {
// Skip providers that fail to instantiate.
}
}
}

$saved_disabled = get_option( 'two_factor_disabled_providers', array() );

echo '<div class="wrap two-factor-settings">';
echo '<h1>' . esc_html__( 'Two-Factor Settings', 'two-factor' ) . '</h1>';
echo '<h2>' . esc_html__( 'Disable Providers', 'two-factor' ) . '</h2>';
echo '<p class="description">' . esc_html__( 'Disable any Two-Factor providers you do not want available on this site. By default all providers are available.', 'two-factor' ) . '</p>';
echo '<form method="post" action="">';
wp_nonce_field( 'two_factor_save_settings', 'two_factor_settings_nonce' );

echo '<fieldset class="two-factor-providers"><legend class="screen-reader-text">' . esc_html__( 'Providers', 'two-factor' ) . '</legend>';
echo '<table class="form-table"><tbody>';

if ( empty( $provider_instances ) ) {
echo '<tr><td>' . esc_html__( 'No providers found.', 'two-factor' ) . '</td></tr>';
} else {
// Render a compact stacked list of provider checkboxes below the title/description.
echo '<tr>';
echo '<td>';
foreach ( $provider_instances as $provider_key => $instance ) {
$label = method_exists( $instance, 'get_label' ) ? $instance->get_label() : $provider_key;

echo '<p class="provider-item"><label for="provider_' . esc_attr( $provider_key ) . '">';
echo '<input type="checkbox" name="two_factor_disabled_providers[]" id="provider_' . esc_attr( $provider_key ) . '" value="' . esc_attr( $provider_key ) . '" ' . checked( in_array( $provider_key, (array) $saved_disabled, true ), true, false ) . ' /> ';
echo esc_html( $label );
echo '</label></p>';
}

echo '</td>';
echo '</tr>';
}

echo '</tbody></table>';
echo '</fieldset>';

submit_button( __( 'Save Settings', 'two-factor' ), 'primary', 'two_factor_settings_submit' );
echo '</form>';

echo '</div>';
}

private static function get_core_default_providers() {
$default_providers = array();
if ( class_exists( 'Two_Factor_Core' ) && method_exists( 'Two_Factor_Core', 'get_default_providers' ) ) {
try {
$rm = new ReflectionMethod( 'Two_Factor_Core', 'get_default_providers' );
if ( ! $rm->isPublic() ) {
$rm->setAccessible( true );
}
if ( $rm->isStatic() ) {
$default_providers = $rm->invoke( null );
} else {
$instance = null;
if ( method_exists( 'Two_Factor_Core', 'get_instance' ) ) {
$instance = call_user_func( array( 'Two_Factor_Core', 'get_instance' ) );
}
if ( $instance ) {
$default_providers = $rm->invoke( $instance );
}
}
} catch ( Throwable $t ) {
$default_providers = array();
}
}
return is_array( $default_providers ) ? $default_providers : array();
}
}
147 changes: 147 additions & 0 deletions two-factor.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,156 @@
*/
require_once TWO_FACTOR_DIR . 'class-two-factor-compat.php';

// Load settings UI class so the settings page can be rendered.
require_once TWO_FACTOR_DIR . 'settings/class-two-factor-settings.php';

$two_factor_compat = new Two_Factor_Compat();

Two_Factor_Core::add_hooks( $two_factor_compat );

// Delete our options and user meta during uninstall.
register_uninstall_hook( __FILE__, array( Two_Factor_Core::class, 'uninstall' ) );

/**
* Register admin menu and plugin action links.
*/
function two_factor_register_admin_hooks() {
if ( is_admin() ) {
add_action( 'admin_menu', 'two_factor_add_settings_page' );
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'two_factor_plugin_action_links' );
}

// Load settings page assets when in admin.
// Settings assets handled inline via standard markup; no extra CSS enqueued.

/* Enforcement filters: restrict providers based on saved disabled option. */
add_filter( 'two_factor_providers', 'two_factor_filter_disabled_providers' );
add_filter( 'two_factor_enabled_providers_for_user', 'two_factor_filter_disabled_enabled_providers_for_user', 10, 2 );
}

add_action( 'init', 'two_factor_register_admin_hooks' );

/**
* Add the Two Factor settings page under Settings.
*/
function two_factor_add_settings_page() {
add_options_page(
__( 'Two-Factor Settings', 'two-factor' ),
__( 'Two-Factor', 'two-factor' ),
'manage_options',
'two-factor-settings',
'two_factor_render_settings_page'
);
}


/**
* Render the settings page via the settings class if available.
*/
function two_factor_render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}

// Prefer new settings class (keeps main file small).
if ( class_exists( 'Two_Factor_Settings' ) && is_callable( array( 'Two_Factor_Settings', 'render_settings_page' ) ) ) {
Two_Factor_Settings::render_settings_page();
return;
}

// Fallback: no UI available.
echo '<div class="wrap"><h1>' . esc_html__( 'Two-Factor Settings', 'two-factor' ) . '</h1>';
echo '<p>' . esc_html__( 'Settings not available.', 'two-factor' ) . '</p></div>';
}


/**
* Helper: retrieve disabled providers option as an array of classnames.
* Empty array / missing option means none disabled (all allowed).
*
* @return array
*/
function two_factor_get_disabled_providers_option() {
$disabled = get_option( 'two_factor_disabled_providers', array() );
if ( empty( $disabled ) || ! is_array( $disabled ) ) {
return array();
}
return $disabled;
}


/**
* Filter the registered providers according to the saved disabled providers option.
* This filter receives the providers in the same shape as core: classname => path.
*/
function two_factor_filter_disabled_providers( $providers ) {
$disabled = two_factor_get_disabled_providers_option();

// Empty disabled list means allow all providers.
if ( empty( $disabled ) ) {
return $providers;
}

// If we are rendering the settings page, do not filter so admins may re-enable providers.
if ( is_admin() && isset( $_GET['page'] ) && 'two-factor-settings' === $_GET['page'] ) {
return $providers;
}

foreach ( $providers as $key => $path ) {
if ( in_array( $key, $disabled, true ) ) {
unset( $providers[ $key ] );
}
}

return $providers;
}


/**
* Filter the supported providers for a specific user (instances keyed by provider key).
*/
function two_factor_filter_disabled_providers_for_user( $providers, $user ) {
$disabled = two_factor_get_disabled_providers_option();
if ( empty( $disabled ) ) {
return $providers;
}

if ( is_admin() && isset( $_GET['page'] ) && 'two-factor-settings' === $_GET['page'] ) {
return $providers;
}

foreach ( $providers as $key => $instance ) {
if ( in_array( $key, $disabled, true ) ) {
unset( $providers[ $key ] );
}
}

return $providers;
}


/**
* Filter enabled providers for a user (classnames array) to enforce disabled list.
*/
function two_factor_filter_disabled_enabled_providers_for_user( $enabled, $user_id ) {
$disabled = two_factor_get_disabled_providers_option();
if ( empty( $disabled ) ) {
return $enabled;
}

return array_values( array_diff( (array) $enabled, $disabled ) );
}


/**
* Add a Settings link on the plugins list that points to our settings page.
*
* @param array $links Existing plugin action links.
* @return array Modified links.
*/
function two_factor_plugin_action_links( $links ) {
$settings_link = '<a href="' . esc_url( admin_url( 'options-general.php?page=two-factor-settings' ) ) . '">' . esc_html__( 'Settings', 'two-factor' ) . '</a>';
array_unshift( $links, $settings_link );

return $links;
}