310 lines
8 KiB
PHP
310 lines
8 KiB
PHP
<?php
|
|
/**
|
|
* Provider filter class
|
|
*
|
|
* @package TwoFactorExtended
|
|
* @since 0.1.0
|
|
*/
|
|
|
|
// Prevent direct access.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class Two_Factor_Extended_Provider_Filter
|
|
*
|
|
* Filters visible 2FA providers based on user roles.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
class Two_Factor_Extended_Provider_Filter {
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
public function __construct() {
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks.
|
|
*
|
|
* @since 0.1.0
|
|
*/
|
|
private function init_hooks(): void {
|
|
// Filter available providers for user.
|
|
add_filter( 'two_factor_providers', array( $this, 'filter_providers_by_role' ), 10, 1 );
|
|
|
|
// Add explanation text in user profile.
|
|
add_action( 'show_user_profile', array( $this, 'render_visibility_explanation' ), 5 );
|
|
add_action( 'edit_user_profile', array( $this, 'render_visibility_explanation' ), 5 );
|
|
}
|
|
|
|
/**
|
|
* Filter providers based on user role.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param array<string, object> $providers Available providers.
|
|
*
|
|
* @return array<string, object> Filtered providers.
|
|
*/
|
|
public function filter_providers_by_role( array $providers ): array {
|
|
// IMPORTANT: Only filter providers on user profile pages, NOT on admin settings pages.
|
|
// Check if we're on a user profile/edit page.
|
|
global $pagenow;
|
|
$is_profile_page = ( 'profile.php' === $pagenow || 'user-edit.php' === $pagenow );
|
|
|
|
// Don't filter on our settings page or other admin pages.
|
|
if ( ! $is_profile_page ) {
|
|
return $providers;
|
|
}
|
|
|
|
// Get current user being edited.
|
|
$user_id = $this->get_profile_user_id();
|
|
|
|
if ( ! $user_id ) {
|
|
return $providers;
|
|
}
|
|
|
|
// Get visible providers for user.
|
|
$visible_providers = $this->get_visible_providers_for_user( $user_id );
|
|
|
|
// If no visibility rules, show all providers.
|
|
if ( null === $visible_providers ) {
|
|
return $providers;
|
|
}
|
|
|
|
// Filter providers.
|
|
$filtered = array();
|
|
|
|
foreach ( $providers as $class_name => $provider ) {
|
|
// Always show required providers.
|
|
if ( $this->is_required_provider( $user_id, $class_name ) ) {
|
|
$filtered[ $class_name ] = $provider;
|
|
continue;
|
|
}
|
|
|
|
// Show if visible for user's roles.
|
|
if ( in_array( $class_name, $visible_providers, true ) ) {
|
|
$filtered[ $class_name ] = $provider;
|
|
}
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
/**
|
|
* Get visible providers for user based on their roles.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param int $user_id User ID.
|
|
*
|
|
* @return array<int, string>|null Array of visible provider class names, or null if no rules.
|
|
*/
|
|
public function get_visible_providers_for_user( int $user_id ): ?array {
|
|
$settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() );
|
|
if ( ! is_array( $settings ) ) {
|
|
$settings = array();
|
|
}
|
|
$user_roles = Two_Factor_Extended_Role_Manager::get_user_roles( $user_id );
|
|
|
|
if ( empty( $user_roles ) || ! isset( $settings['provider_visibility'] ) || ! is_array( $settings['provider_visibility'] ) ) {
|
|
return null;
|
|
}
|
|
|
|
$visible = array();
|
|
|
|
// Collect visible providers from all user roles.
|
|
foreach ( $user_roles as $role ) {
|
|
if ( isset( $settings['provider_visibility'][ $role ] ) ) {
|
|
$role_visible = $settings['provider_visibility'][ $role ];
|
|
|
|
if ( is_array( $role_visible ) ) {
|
|
foreach ( $role_visible as $provider ) {
|
|
if ( is_string( $provider ) ) {
|
|
$visible[] = $provider;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no visibility rules for any role, return null (show all).
|
|
if ( empty( $visible ) ) {
|
|
return null;
|
|
}
|
|
|
|
return array_values( array_unique( $visible ) );
|
|
}
|
|
|
|
/**
|
|
* Check if provider is required for user.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param int $user_id User ID.
|
|
* @param string $provider_class Provider class name.
|
|
*
|
|
* @return bool True if provider is required.
|
|
*/
|
|
private function is_required_provider( int $user_id, string $provider_class ): bool {
|
|
$enforcement = two_factor_extended()->get_enforcement();
|
|
|
|
if ( ! $enforcement ) {
|
|
return false;
|
|
}
|
|
|
|
$required_providers = $enforcement->get_required_providers_for_user( $user_id );
|
|
|
|
return in_array( $provider_class, $required_providers, true );
|
|
}
|
|
|
|
/**
|
|
* Get user ID from profile page context.
|
|
*
|
|
* Uses filter_input() for safe GET parameter access when determining
|
|
* which user's profile is being viewed. This is a read-only operation
|
|
* used for displaying the correct 2FA provider options.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return int User ID.
|
|
*/
|
|
private function get_profile_user_id(): int {
|
|
// Check if editing another user's profile (e.g., wp-admin/user-edit.php?user_id=123).
|
|
// Use filter_input() for safe access to GET parameter.
|
|
$user_id = filter_input( INPUT_GET, 'user_id', FILTER_VALIDATE_INT );
|
|
|
|
if ( $user_id && $user_id > 0 ) {
|
|
return $user_id;
|
|
}
|
|
|
|
// Check if editing own profile.
|
|
return get_current_user_id();
|
|
}
|
|
|
|
/**
|
|
* Render visibility explanation in user profile.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param WP_User $user User object.
|
|
*/
|
|
public function render_visibility_explanation( WP_User $user ): void {
|
|
$visible_providers = $this->get_visible_providers_for_user( $user->ID );
|
|
|
|
// No visibility rules set.
|
|
if ( null === $visible_providers ) {
|
|
return;
|
|
}
|
|
|
|
$all_providers = Two_Factor_Extended_Provider_Detector::get_all_providers();
|
|
$enforcement = two_factor_extended()->get_enforcement();
|
|
$provider_names = Two_Factor_Extended_Provider_Detector::get_provider_names();
|
|
|
|
$required_providers = null !== $enforcement
|
|
? $enforcement->get_required_providers_for_user( $user->ID )
|
|
: array();
|
|
|
|
// Calculate hidden providers.
|
|
$hidden_providers = array();
|
|
|
|
foreach ( array_keys( $all_providers ) as $class ) {
|
|
// Skip required providers (always visible).
|
|
if ( in_array( $class, $required_providers, true ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Not in visible list = hidden.
|
|
if ( ! in_array( $class, $visible_providers, true ) ) {
|
|
$hidden_providers[] = $provider_names[ $class ] ?? $class;
|
|
}
|
|
}
|
|
|
|
if ( empty( $hidden_providers ) ) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<div class="notice notice-info inline">
|
|
<p>
|
|
<strong><?php esc_html_e( 'Two-Factor Extended:', 'two-factor-extended' ); ?></strong>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: List of hidden providers */
|
|
esc_html__( 'Some 2FA methods are hidden based on your role: %s', 'two-factor-extended' ),
|
|
'<strong>' . esc_html( implode( ', ', $hidden_providers ) ) . '</strong>'
|
|
);
|
|
?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Get provider visibility inheritance for multiple roles.
|
|
*
|
|
* Implements union logic: user sees providers visible to ANY of their roles.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param array<string> $roles Array of role slugs.
|
|
*
|
|
* @return array<string> Array of visible provider class names.
|
|
*/
|
|
public function get_inherited_visibility( array $roles ): array {
|
|
$settings = get_option( TWO_FACTOR_EXTENDED_OPTION_SETTINGS, array() );
|
|
$visible = array();
|
|
|
|
if ( ! is_array( $settings ) || ! isset( $settings['provider_visibility'] ) || ! is_array( $settings['provider_visibility'] ) ) {
|
|
return array();
|
|
}
|
|
|
|
foreach ( $roles as $role ) {
|
|
if ( isset( $settings['provider_visibility'][ $role ] ) ) {
|
|
$role_visible = $settings['provider_visibility'][ $role ];
|
|
|
|
if ( is_array( $role_visible ) ) {
|
|
foreach ( $role_visible as $provider ) {
|
|
if ( is_string( $provider ) ) {
|
|
$visible[] = $provider;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values( array_unique( $visible ) );
|
|
}
|
|
|
|
/**
|
|
* Check if provider is visible for user.
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @param int $user_id User ID.
|
|
* @param string $provider_class Provider class name.
|
|
*
|
|
* @return bool True if provider is visible, false otherwise.
|
|
*/
|
|
public function is_provider_visible( int $user_id, string $provider_class ): bool {
|
|
// Required providers are always visible.
|
|
if ( $this->is_required_provider( $user_id, $provider_class ) ) {
|
|
return true;
|
|
}
|
|
|
|
$visible_providers = $this->get_visible_providers_for_user( $user_id );
|
|
|
|
// No visibility rules = all visible.
|
|
if ( null === $visible_providers ) {
|
|
return true;
|
|
}
|
|
|
|
return in_array( $provider_class, $visible_providers, true );
|
|
}
|
|
}
|