# password-reset-enforcement/trunk/src/modules/cli/class-module-cli.php

Teydea Password Reset – Force Password Reset &amp; Expiration, version trunk. 460 lines.

- Page: https://pluginprobe.com/plugins/password-reset-enforcement/trunk/code/src/modules/cli/class-module-cli.php
- Raw: https://pluginprobe.com/plugins/password-reset-enforcement/trunk/raw/src/modules/cli/class-module-cli.php
- Modified: 2026-08-27T22:13:58+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/password-reset-enforcement/trunk/code/src/modules/cli/class-module-cli.php#L10-L20`.

```php
<?php
/**
 * CLI commands used for managing the password reset enforcement across users.
 *
 * @package Teydea_Studio\Password_Reset
 */

namespace Teydea_Studio\Password_Reset\Modules\CLI;

use Teydea_Studio\Password_Reset\Dependencies\Utils;
use Teydea_Studio\Password_Reset\Processing;
use WP_CLI;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // @codeCoverageIgnore
}

/**
 * The "Module_CLI" class
 */
final class Module_CLI extends Utils\Module {
	/**
	 * Register hooks
	 *
	 * @return void
	 */
	public function register(): void {
		// Register the WP-CLI commands only if WP-CLI is available.
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			// Clear password reset enforcement for users.
			WP_CLI::add_command( 'password-reset-enforcement clear', [ $this, 'handle_clear_request' ] );

			// Force password reset for users.
			WP_CLI::add_command( 'password-reset-enforcement force', [ $this, 'handle_force_request' ] );

			// List users for whom the password reset has been enforced.
			WP_CLI::add_command( 'password-reset-enforcement list', [ $this, 'handle_list_request' ] );

			// Check status of password reset enforcement for users.
			WP_CLI::add_command( 'password-reset-enforcement status', [ $this, 'handle_status_request' ] );
		}
	}

	/**
	 * Clear password reset enforcement for users
	 *
	 * Clears password reset enforcement for specified users across
	 * the WordPress site. Users can be targeted by role
	 * or individually.
	 *
	 * ## OPTIONS
	 *
	 * [--to_all]
	 * : Clear password reset enforcement for all users on the site.
	 * ---
	 * default: false
	 * ---
	 *
	 * [--to_roles=<roles>]
	 * : Comma-separated list of user roles to target for password reset.
	 *
	 * [--to_users=<user_ids>]
	 * : Comma-separated list of specific user IDs to target for password reset.
	 *
	 * [--limit=<number>]
	 * : Maximum number of users to process in a single operation.
	 *
	 * [--paged=<page>]
	 * : Page number for pagination when processing large user sets.
	 *
	 * ## EXAMPLES
	 *
	 *     # Clear password reset enforcement for all users immediately.
	 *     wp password-reset-enforcement clear --to_all
	 *
	 *     # Clear password reset enforcement for specific users.
	 *     wp password-reset-enforcement clear --to_users=1,5,10
	 *
	 *     # Clear password reset enforcement for editors and administrators.
	 *     wp password-reset-enforcement clear --to_roles=editor,administrator
	 *
	 *     # Process users in batches with pagination.
	 *     wp password-reset-enforcement clear --to_all --limit=50 --paged=2
	 *
	 * @param array $args       Positional arguments (unused in this command).
	 * @param array $assoc_args Associative arguments containing targeting and configuration options.
	 *
	 * @return void
	 */
	public function handle_clear_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
		$default_args = [
			'to_all'   => false,
			'to_roles' => [],
			'to_users' => [],
			'limit'    => null,
			'paged'    => null,
		];

		/** @var array{to_all:bool,to_roles:string[],to_users:int[],limit:?int,paged:?int} $assoc_args */
		$assoc_args = $this->validate_and_sanitize_args(
			wp_parse_args( $assoc_args, $default_args ),
			array_keys( $default_args ),
		);

		$processing = new Processing( $this->container );

		$user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );

		$processing->clear_password_reset_enforcement( $user_ids );

		if ( empty( $user_ids ) ) {
			WP_CLI::success( 'No users were processed.' );
		} else {
			WP_CLI::success( 'Password reset enforcement has been cleared for user(s) with following ID(s):' );
			WP_CLI::line( implode( ', ', $user_ids ) );
		}
	}

	/**
	 * Force password reset for users
	 *
	 * Enforces password reset for specified users across the WordPress site. Users can be
	 * targeted by specific IDs, user roles, or all users at once. The command provides
	 * flexible options for controlling when the reset takes effect, whether email
	 * notifications are sent, and pagination for bulk operations.
	 *
	 * ## OPTIONS
	 *
	 * [--to_all]
	 * : Force password reset for all users on the site.
	 * ---
	 * default: false
	 * ---
	 *
	 * [--to_roles=<roles>]
	 * : Comma-separated list of user roles to target for password reset.
	 *
	 * [--to_users=<user_ids>]
	 * : Comma-separated list of specific user IDs to target for password reset.
	 *
	 * [--applicability=<when>]
	 * : When the password reset should take effect.
	 * ---
	 * default: immediately
	 * options:
	 *   - immediately
	 *   - after_session_expiry
	 * ---
	 *
	 * [--with_email]
	 * : Whether to send email notifications to affected users.
	 * ---
	 * default: true
	 * ---
	 *
	 * [--with_current_password_allowed]
	 * : Whether users can reuse their current password during reset.
	 * ---
	 * default: false
	 * ---
	 *
	 * [--limit=<number>]
	 * : Maximum number of users to process in a single operation.
	 *
	 * [--paged=<page>]
	 * : Page number for pagination when processing large user sets.
	 *
	 * ## EXAMPLES
	 *
	 *     # Force password reset for all users immediately.
	 *     wp password-reset-enforcement force --to_all
	 *
	 *     # Force password reset for specific users without email notifications.
	 *     wp password-reset-enforcement force --to_users=1,5,10 --with_email=false
	 *
	 *     # Force password reset for editors and administrators after current session expiry.
	 *     wp password-reset-enforcement force --to_roles=editor,administrator --applicability=after_session_expiry
	 *
	 *     # Process users in batches with pagination.
	 *     wp password-reset-enforcement force --to_all --limit=50 --paged=2
	 *
	 *     # Allow users to keep their current password during reset.
	 *     wp password-reset-enforcement force --to_roles=subscriber --with_current_password_allowed=true
	 *
	 * @param array $args       Positional arguments (unused in this command).
	 * @param array $assoc_args Associative arguments containing targeting and configuration options.
	 *
	 * @return void
	 */
	public function handle_force_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
		$default_args = [
			'to_all'                        => false,
			'to_roles'                      => [],
			'to_users'                      => [],
			'applicability'                 => Processing::APPLICABILITY_IMMEDIATELY,
			'with_email'                    => true,
			'with_current_password_allowed' => false,
			'limit'                         => null,
			'paged'                         => null,
		];

		/** @var array{to_all:bool,to_roles:string[],to_users:int[],limit:?int,paged:?int,with_email:bool,with_current_password_allowed:bool,applicability:string} $assoc_args */
		$assoc_args = $this->validate_and_sanitize_args(
			wp_parse_args( $assoc_args, $default_args ),
			array_keys( $default_args ),
		);

		$processing = new Processing( $this->container );

		$user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );

		$processing->force_password_reset(
			$user_ids,
			$assoc_args['applicability'],
			$assoc_args['with_email'],
			$assoc_args['with_current_password_allowed'],
			'WP-CLI',
		);

		if ( empty( $user_ids ) ) {
			WP_CLI::success( 'No users were processed.' );
		} else {
			WP_CLI::success( 'Password reset enforced for user(s) with following ID(s):' );
			WP_CLI::line( implode( ', ', $user_ids ) );
		}
	}

	/**
	 * List users for whom the password reset has been enforced
	 *
	 * Lists users for whom the password reset enforcement is active across
	 * the WordPress site.
	 *
	 * ## OPTIONS
	 *
	 * [--limit=<number>]
	 * : Maximum number of users to process in a single operation.
	 *
	 * [--paged=<page>]
	 * : Page number for pagination when processing large user sets.
	 *
	 * ## EXAMPLES
	 *
	 *     # List users with enforced password reset.
	 *     wp password-reset-enforcement list
	 *
	 *     # Process users in batches with pagination.
	 *     wp password-reset-enforcement list --limit=50 --paged=2
	 *
	 * @param array $args       Positional arguments (unused in this command).
	 * @param array $assoc_args Associative arguments containing targeting and configuration options.
	 *
	 * @return void
	 */
	public function handle_list_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
		$default_args = [
			'limit' => null,
			'paged' => null,
		];

		/** @var array{limit:?int,paged:?int} $assoc_args */
		$assoc_args = $this->validate_and_sanitize_args(
			wp_parse_args( $assoc_args, $default_args ),
			array_keys( $default_args ),
		);

		$processing = new Processing( $this->container );
		$users      = $processing->list_users_with_enforced_password_reset(
			$assoc_args['limit'],
			$assoc_args['paged'],
		);

		if ( empty( $users ) ) {
			WP_CLI::success( 'No users found.' );
		} else {
			WP_CLI\Utils\format_items( 'table', $users, [ 'user_id', 'user_name', 'requested_at', 'requested_by', 'with_current_password_allowed' ] );
		}
	}

	/**
	 * Check status of password reset enforcement for users
	 *
	 * Checks the status of password reset enforcement for specified users across
	 * the WordPress site. Users can be targeted by role
	 * or individually.
	 *
	 * ## OPTIONS
	 *
	 * [--to_all]
	 * : Check password reset status for all users on the site.
	 * ---
	 * default: false
	 * ---
	 *
	 * [--to_roles=<roles>]
	 * : Comma-separated list of user roles to target for password reset.
	 *
	 * [--to_users=<user_ids>]
	 * : Comma-separated list of specific user IDs to target for password reset.
	 *
	 * [--limit=<number>]
	 * : Maximum number of users to process in a single operation.
	 *
	 * [--paged=<page>]
	 * : Page number for pagination when processing large user sets.
	 *
	 * ## EXAMPLES
	 *
	 *     # Check password reset enforcement status for all users.
	 *     wp password-reset-enforcement status --to_all
	 *
	 *     # Check password reset enforcement status for specific users.
	 *     wp password-reset-enforcement status --to_users=1,5,10
	 *
	 *     # Check password reset enforcement status for editors and administrators.
	 *     wp password-reset-enforcement status --to_roles=editor,administrator
	 *
	 *     # Process users in batches with pagination.
	 *     wp password-reset-enforcement status --to_all --limit=50 --paged=2
	 *
	 * @param array $args       Positional arguments (unused in this command).
	 * @param array $assoc_args Associative arguments containing targeting and configuration options.
	 *
	 * @return void
	 */
	public function handle_status_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
		$default_args = [
			'to_all'   => false,
			'to_roles' => [],
			'to_users' => [],
			'limit'    => null,
			'paged'    => null,
		];

		/** @var array{to_all:bool,to_roles:string[],to_users:int[],limit:?int,paged:?int} $assoc_args */
		$assoc_args = $this->validate_and_sanitize_args(
			wp_parse_args( $assoc_args, $default_args ),
			array_keys( $default_args ),
		);

		$processing = new Processing( $this->container );

		$user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );

		$status = $processing->get_password_reset_enforcement_status( $user_ids );

		if ( empty( $status ) ) {
			WP_CLI::success( 'No users were processed.' );
		} else {
			WP_CLI\Utils\format_items( 'table', $status, [ 'user_id', 'user_name', 'needs_password_reset', 'requested_at', 'requested_by', 'with_current_password_allowed' ] );
		}
	}

	/**
	 * Validate and sanitize common CLI arguments
	 *
	 * @param array $assoc_args   Associative arguments array.
	 * @param array $allowed_args Array of allowed argument names for this command.
	 *
	 * @return array{to_all?:bool,to_roles?:string[],to_users?:int[],limit?:?int,paged?:?int,with_email?:bool,with_current_password_allowed?:bool,applicability?:string} Sanitized associative arguments array.
	 */
	protected function validate_and_sanitize_args( array $assoc_args, array $allowed_args = [] ): array /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
		// Convert boolean arguments.
		if ( in_array( 'to_all', $allowed_args, true ) ) {
			$assoc_args['to_all'] = wp_validate_boolean( $assoc_args['to_all'] );
		}

		if ( in_array( 'with_email', $allowed_args, true ) ) {
			$assoc_args['with_email'] = wp_validate_boolean( $assoc_args['with_email'] );
		}

		if ( in_array( 'with_current_password_allowed', $allowed_args, true ) ) {
			$assoc_args['with_current_password_allowed'] = wp_validate_boolean( $assoc_args['with_current_password_allowed'] );
		}

		// Convert pagination arguments.
		if ( in_array( 'limit', $allowed_args, true ) ) {
			$assoc_args['limit'] = ! is_null( $assoc_args['limit'] ) ? absint( $assoc_args['limit'] ) : null;
		}

		if ( in_array( 'paged', $allowed_args, true ) ) {
			$assoc_args['paged'] = ! is_null( $assoc_args['paged'] ) ? absint( $assoc_args['paged'] ) : null;
		}

		// Convert comma-separated strings to arrays.
		if ( in_array( 'to_roles', $allowed_args, true ) && is_string( $assoc_args['to_roles'] ) ) {
			$assoc_args['to_roles'] = array_values(
				array_filter(
					array_map(
						fn ( string $role ): string => Utils\Strings::trim( $role ),
						explode( ',', $assoc_args['to_roles'] ),
					),
				),
			);
		}

		if ( in_array( 'to_users', $allowed_args, true ) && is_string( $assoc_args['to_users'] ) ) {
			$assoc_args['to_users'] = array_values(
				array_filter(
					array_map(
						fn ( string $user_id ): string => Utils\Strings::trim( $user_id ),
						explode( ',', $assoc_args['to_users'] ),
					),
				),
			);

			if ( ! empty( $assoc_args['to_users'] ) ) {
				$users                  = new Utils\Users( $this->container );
				$assoc_args['to_users'] = $users->maybe_map_user_logins_to_user_ids( $assoc_args['to_users'] );
			}
		}

		// Validate applicability option (only if present in allowed args).
		if ( in_array( 'applicability', $allowed_args, true ) ) {
			if ( ! in_array( $assoc_args['applicability'], Processing::APPLICABILITIES, true ) ) {
				WP_CLI::error( sprintf( 'Invalid applicability value. Must be one of: %s', implode( ', ', Processing::APPLICABILITIES ) ) );
			}
		}

		// Validate at least one targeting option is provided.
		if ( in_array( 'to_all', $allowed_args, true ) || in_array( 'to_roles', $allowed_args, true ) || in_array( 'to_users', $allowed_args, true ) ) {
			$has_targeting = false;

			if ( in_array( 'to_all', $allowed_args, true ) && $assoc_args['to_all'] ) {
				$has_targeting = true;
			}

			if ( in_array( 'to_roles', $allowed_args, true ) && ! empty( $assoc_args['to_roles'] ) ) {
				$has_targeting = true;
			}

			if ( in_array( 'to_users', $allowed_args, true ) && ! empty( $assoc_args['to_users'] ) ) {
				$has_targeting = true;
			}

			if ( ! $has_targeting ) {
				WP_CLI::error( 'You must specify at least one targeting option: --to_all, --to_roles, --to_users.' );
			}
		}

		// Validate pagination parameters (only if provided).
		if ( in_array( 'limit', $allowed_args, true ) && ! is_null( $assoc_args['limit'] ) && $assoc_args['limit'] < 1 ) {
			WP_CLI::error( 'Limit must be a positive integer.' );
		}

		if ( in_array( 'paged', $allowed_args, true ) && ! is_null( $assoc_args['paged'] ) && $assoc_args['paged'] < 1 ) {
			WP_CLI::error( 'Page number must be a positive integer.' );
		}

		// Validate that paged cannot be used without limit.
		if ( in_array( 'paged', $allowed_args, true ) && in_array( 'limit', $allowed_args, true ) ) {
			if ( ! is_null( $assoc_args['paged'] ) && is_null( $assoc_args['limit'] ) ) {
				WP_CLI::error( 'You cannot specify --paged without also specifying --limit.' );
			}
		}

		return $assoc_args;
	}
}

```
