two-factor-extended/includes/class-bulk-actions.php
2026-03-28 10:06:01 +00:00

280 lines
6.5 KiB
PHP

<?php
/**
* Bulk actions handler
*
* @package TwoFactorExtended
* @since 0.1.0
*/
// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Two_Factor_Extended_Bulk_Actions
*
* Handles bulk actions for users.
*
* @since 0.1.0
*/
class Two_Factor_Extended_Bulk_Actions {
/**
* Constructor.
*
* @since 0.1.0
*/
public function __construct() {
$this->init_hooks();
}
/**
* Initialize WordPress hooks.
*
* @since 0.1.0
*/
private function init_hooks(): void {
// Add bulk actions to users list.
add_filter( 'bulk_actions-users', array( $this, 'register_bulk_actions' ) );
// Handle bulk actions.
add_filter( 'handle_bulk_actions-users', array( $this, 'handle_bulk_actions' ), 10, 3 );
// Display admin notices after bulk actions.
add_action( 'admin_notices', array( $this, 'display_bulk_action_notices' ) );
}
/**
* Register bulk actions.
*
* @since 0.1.0
*
* @param array<string, string> $actions Existing bulk actions.
*
* @return array<string, string> Modified bulk actions.
*/
public function register_bulk_actions( array $actions ): array {
if ( ! current_user_can( 'manage_options' ) ) {
return $actions;
}
$actions['two_factor_extended_require'] = __( 'Require 2FA Setup', 'two-factor-extended' );
$actions['two_factor_extended_reset'] = __( 'Reset 2FA Grace Period', 'two-factor-extended' );
return $actions;
}
/**
* Handle bulk actions.
*
* @since 0.1.0
*
* @param string $redirect_to Redirect URL.
* @param string $action Action name.
* @param int[] $user_ids User IDs.
*
* @return string Modified redirect URL.
*/
public function handle_bulk_actions( string $redirect_to, string $action, array $user_ids ): string {
// Check capability.
if ( ! current_user_can( 'manage_options' ) ) {
return $redirect_to;
}
// Handle our bulk actions.
if ( 'two_factor_extended_require' === $action ) {
$processed = $this->bulk_require_2fa( $user_ids );
$redirect_to = add_query_arg(
array(
'two_factor_extended_bulk_require' => $processed,
),
$redirect_to
);
} elseif ( 'two_factor_extended_reset' === $action ) {
$processed = $this->bulk_reset_grace_period( $user_ids );
$redirect_to = add_query_arg(
array(
'two_factor_extended_bulk_reset' => $processed,
),
$redirect_to
);
}
return $redirect_to;
}
/**
* Bulk require 2FA for users.
*
* @since 0.1.0
*
* @param int[] $user_ids User IDs.
*
* @return int Number of users processed.
*/
private function bulk_require_2fa( array $user_ids ): int {
$processed = 0;
$enforcement = two_factor_extended()->get_enforcement();
if ( null === $enforcement ) {
return 0;
}
foreach ( $user_ids as $user_id ) {
// Skip if user doesn't exist.
$user = get_userdata( $user_id );
if ( ! $user ) {
continue;
}
// Get required providers for user.
$required = $enforcement->get_required_providers_for_user( $user_id );
// Skip if no requirements.
if ( empty( $required ) ) {
continue;
}
// Set enforcement start date to now (starts grace period).
update_user_meta( $user_id, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() );
// Log the action.
$audit_log = two_factor_extended()->get_audit_log();
if ( null !== $audit_log ) {
$audit_log->log_event(
'bulk_require_2fa',
sprintf(
'Bulk action: Require 2FA for user %s',
$user->user_login
),
$user_id,
array(
'required_providers' => $required,
)
);
}
++$processed;
}
return $processed;
}
/**
* Bulk reset grace period for users.
*
* @since 0.1.0
*
* @param int[] $user_ids User IDs.
*
* @return int Number of users processed.
*/
private function bulk_reset_grace_period( array $user_ids ): int {
$processed = 0;
foreach ( $user_ids as $user_id ) {
// Skip if user doesn't exist.
$user = get_userdata( $user_id );
if ( ! $user ) {
continue;
}
// Reset enforcement start date (restarts grace period).
update_user_meta( $user_id, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() );
// Clear grace period notified flag.
delete_user_meta( $user_id, Two_Factor_Extended_Enforcement::META_GRACE_NOTIFIED );
// Log the action.
$audit_log = two_factor_extended()->get_audit_log();
if ( null !== $audit_log ) {
$audit_log->log_event(
'bulk_reset_grace',
sprintf(
'Bulk action: Reset grace period for user %s',
$user->user_login
),
$user_id
);
}
++$processed;
}
return $processed;
}
/**
* Display admin notices after bulk actions.
*
* This method displays success messages after bulk actions are completed.
* No nonce verification is needed here because:
* 1. This is a read-only operation (displaying a message)
* 2. The actual bulk action was already verified in handle_bulk_actions()
* 3. The GET parameters are the result of a POST-redirect-GET pattern
* 4. Only the count is used, which is safely cast to int
* 5. User capability is verified before displaying
*
* @since 0.1.0
*/
public function display_bulk_action_notices(): void {
// Check if on users page.
$screen = get_current_screen();
if ( ! $screen || 'users' !== $screen->id ) {
return;
}
// Verify user has capability to see these notices.
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Bulk require 2FA notice.
// Use filter_input() for safe GET parameter access.
$bulk_require_count = filter_input( INPUT_GET, 'two_factor_extended_bulk_require', FILTER_VALIDATE_INT );
if ( $bulk_require_count && $bulk_require_count > 0 ) {
printf(
'<div class="notice notice-success is-dismissible"><p>%s</p></div>',
esc_html(
sprintf(
/* translators: %d: Number of users */
_n(
'2FA requirement set for %d user.',
'2FA requirement set for %d users.',
$bulk_require_count,
'two-factor-extended'
),
$bulk_require_count
)
)
);
}
// Bulk reset grace period notice.
$bulk_reset_count = filter_input( INPUT_GET, 'two_factor_extended_bulk_reset', FILTER_VALIDATE_INT );
if ( $bulk_reset_count && $bulk_reset_count > 0 ) {
printf(
'<div class="notice notice-success is-dismissible"><p>%s</p></div>',
esc_html(
sprintf(
/* translators: %d: Number of users */
_n(
'Grace period reset for %d user.',
'Grace period reset for %d users.',
$bulk_reset_count,
'two-factor-extended'
),
$bulk_reset_count
)
)
);
}
}
}