PluginProbe
Teydea Password Reset – Force Password Reset & Expiration / trunk
Teydea Password Reset – Force Password Reset & Expiration vtrunk
trunk 1.0.0 1.1.0 1.1.1 1.10.0 1.10.1 1.10.2 1.11.0 1.11.1 1.12.0 1.12.1 1.13.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.7.1 1.7.2 1.8.0 1.9.0
password-reset-enforcement / src / modules / cli / class-module-cli.php

class-module-cli.php in Teydea Password Reset – Force Password Reset & Expiration trunk, at src/modules/cli/class-module-cli.php

460 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CLI commands used for managing the password reset enforcement across users.
4 *
5 * @package Teydea_Studio\Password_Reset
6 */
7
8 namespace Teydea_Studio\Password_Reset\Modules\CLI;
9
10 use Teydea_Studio\Password_Reset\Dependencies\Utils;
11 use Teydea_Studio\Password_Reset\Processing;
12 use WP_CLI;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // @codeCoverageIgnore
16 }
17
18 /**
19 * The "Module_CLI" class
20 */
21 final class Module_CLI extends Utils\Module {
22 /**
23 * Register hooks
24 *
25 * @return void
26 */
27 public function register(): void {
28 // Register the WP-CLI commands only if WP-CLI is available.
29 if ( defined( 'WP_CLI' ) && WP_CLI ) {
30 // Clear password reset enforcement for users.
31 WP_CLI::add_command( 'password-reset-enforcement clear', [ $this, 'handle_clear_request' ] );
32
33 // Force password reset for users.
34 WP_CLI::add_command( 'password-reset-enforcement force', [ $this, 'handle_force_request' ] );
35
36 // List users for whom the password reset has been enforced.
37 WP_CLI::add_command( 'password-reset-enforcement list', [ $this, 'handle_list_request' ] );
38
39 // Check status of password reset enforcement for users.
40 WP_CLI::add_command( 'password-reset-enforcement status', [ $this, 'handle_status_request' ] );
41 }
42 }
43
44 /**
45 * Clear password reset enforcement for users
46 *
47 * Clears password reset enforcement for specified users across
48 * the WordPress site. Users can be targeted by role
49 * or individually.
50 *
51 * ## OPTIONS
52 *
53 * [--to_all]
54 * : Clear password reset enforcement for all users on the site.
55 * ---
56 * default: false
57 * ---
58 *
59 * [--to_roles=<roles>]
60 * : Comma-separated list of user roles to target for password reset.
61 *
62 * [--to_users=<user_ids>]
63 * : Comma-separated list of specific user IDs to target for password reset.
64 *
65 * [--limit=<number>]
66 * : Maximum number of users to process in a single operation.
67 *
68 * [--paged=<page>]
69 * : Page number for pagination when processing large user sets.
70 *
71 * ## EXAMPLES
72 *
73 * # Clear password reset enforcement for all users immediately.
74 * wp password-reset-enforcement clear --to_all
75 *
76 * # Clear password reset enforcement for specific users.
77 * wp password-reset-enforcement clear --to_users=1,5,10
78 *
79 * # Clear password reset enforcement for editors and administrators.
80 * wp password-reset-enforcement clear --to_roles=editor,administrator
81 *
82 * # Process users in batches with pagination.
83 * wp password-reset-enforcement clear --to_all --limit=50 --paged=2
84 *
85 * @param array $args Positional arguments (unused in this command).
86 * @param array $assoc_args Associative arguments containing targeting and configuration options.
87 *
88 * @return void
89 */
90 public function handle_clear_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
91 $default_args = [
92 'to_all' => false,
93 'to_roles' => [],
94 'to_users' => [],
95 'limit' => null,
96 'paged' => null,
97 ];
98
99 /** @var array{to_all:bool,to_roles:string[],to_users:int[],limit:?int,paged:?int} $assoc_args */
100 $assoc_args = $this->validate_and_sanitize_args(
101 wp_parse_args( $assoc_args, $default_args ),
102 array_keys( $default_args ),
103 );
104
105 $processing = new Processing( $this->container );
106
107 $user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );
108
109 $processing->clear_password_reset_enforcement( $user_ids );
110
111 if ( empty( $user_ids ) ) {
112 WP_CLI::success( 'No users were processed.' );
113 } else {
114 WP_CLI::success( 'Password reset enforcement has been cleared for user(s) with following ID(s):' );
115 WP_CLI::line( implode( ', ', $user_ids ) );
116 }
117 }
118
119 /**
120 * Force password reset for users
121 *
122 * Enforces password reset for specified users across the WordPress site. Users can be
123 * targeted by specific IDs, user roles, or all users at once. The command provides
124 * flexible options for controlling when the reset takes effect, whether email
125 * notifications are sent, and pagination for bulk operations.
126 *
127 * ## OPTIONS
128 *
129 * [--to_all]
130 * : Force password reset for all users on the site.
131 * ---
132 * default: false
133 * ---
134 *
135 * [--to_roles=<roles>]
136 * : Comma-separated list of user roles to target for password reset.
137 *
138 * [--to_users=<user_ids>]
139 * : Comma-separated list of specific user IDs to target for password reset.
140 *
141 * [--applicability=<when>]
142 * : When the password reset should take effect.
143 * ---
144 * default: immediately
145 * options:
146 * - immediately
147 * - after_session_expiry
148 * ---
149 *
150 * [--with_email]
151 * : Whether to send email notifications to affected users.
152 * ---
153 * default: true
154 * ---
155 *
156 * [--with_current_password_allowed]
157 * : Whether users can reuse their current password during reset.
158 * ---
159 * default: false
160 * ---
161 *
162 * [--limit=<number>]
163 * : Maximum number of users to process in a single operation.
164 *
165 * [--paged=<page>]
166 * : Page number for pagination when processing large user sets.
167 *
168 * ## EXAMPLES
169 *
170 * # Force password reset for all users immediately.
171 * wp password-reset-enforcement force --to_all
172 *
173 * # Force password reset for specific users without email notifications.
174 * wp password-reset-enforcement force --to_users=1,5,10 --with_email=false
175 *
176 * # Force password reset for editors and administrators after current session expiry.
177 * wp password-reset-enforcement force --to_roles=editor,administrator --applicability=after_session_expiry
178 *
179 * # Process users in batches with pagination.
180 * wp password-reset-enforcement force --to_all --limit=50 --paged=2
181 *
182 * # Allow users to keep their current password during reset.
183 * wp password-reset-enforcement force --to_roles=subscriber --with_current_password_allowed=true
184 *
185 * @param array $args Positional arguments (unused in this command).
186 * @param array $assoc_args Associative arguments containing targeting and configuration options.
187 *
188 * @return void
189 */
190 public function handle_force_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
191 $default_args = [
192 'to_all' => false,
193 'to_roles' => [],
194 'to_users' => [],
195 'applicability' => Processing::APPLICABILITY_IMMEDIATELY,
196 'with_email' => true,
197 'with_current_password_allowed' => false,
198 'limit' => null,
199 'paged' => null,
200 ];
201
202 /** @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 */
203 $assoc_args = $this->validate_and_sanitize_args(
204 wp_parse_args( $assoc_args, $default_args ),
205 array_keys( $default_args ),
206 );
207
208 $processing = new Processing( $this->container );
209
210 $user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );
211
212 $processing->force_password_reset(
213 $user_ids,
214 $assoc_args['applicability'],
215 $assoc_args['with_email'],
216 $assoc_args['with_current_password_allowed'],
217 'WP-CLI',
218 );
219
220 if ( empty( $user_ids ) ) {
221 WP_CLI::success( 'No users were processed.' );
222 } else {
223 WP_CLI::success( 'Password reset enforced for user(s) with following ID(s):' );
224 WP_CLI::line( implode( ', ', $user_ids ) );
225 }
226 }
227
228 /**
229 * List users for whom the password reset has been enforced
230 *
231 * Lists users for whom the password reset enforcement is active across
232 * the WordPress site.
233 *
234 * ## OPTIONS
235 *
236 * [--limit=<number>]
237 * : Maximum number of users to process in a single operation.
238 *
239 * [--paged=<page>]
240 * : Page number for pagination when processing large user sets.
241 *
242 * ## EXAMPLES
243 *
244 * # List users with enforced password reset.
245 * wp password-reset-enforcement list
246 *
247 * # Process users in batches with pagination.
248 * wp password-reset-enforcement list --limit=50 --paged=2
249 *
250 * @param array $args Positional arguments (unused in this command).
251 * @param array $assoc_args Associative arguments containing targeting and configuration options.
252 *
253 * @return void
254 */
255 public function handle_list_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
256 $default_args = [
257 'limit' => null,
258 'paged' => null,
259 ];
260
261 /** @var array{limit:?int,paged:?int} $assoc_args */
262 $assoc_args = $this->validate_and_sanitize_args(
263 wp_parse_args( $assoc_args, $default_args ),
264 array_keys( $default_args ),
265 );
266
267 $processing = new Processing( $this->container );
268 $users = $processing->list_users_with_enforced_password_reset(
269 $assoc_args['limit'],
270 $assoc_args['paged'],
271 );
272
273 if ( empty( $users ) ) {
274 WP_CLI::success( 'No users found.' );
275 } else {
276 WP_CLI\Utils\format_items( 'table', $users, [ 'user_id', 'user_name', 'requested_at', 'requested_by', 'with_current_password_allowed' ] );
277 }
278 }
279
280 /**
281 * Check status of password reset enforcement for users
282 *
283 * Checks the status of password reset enforcement for specified users across
284 * the WordPress site. Users can be targeted by role
285 * or individually.
286 *
287 * ## OPTIONS
288 *
289 * [--to_all]
290 * : Check password reset status for all users on the site.
291 * ---
292 * default: false
293 * ---
294 *
295 * [--to_roles=<roles>]
296 * : Comma-separated list of user roles to target for password reset.
297 *
298 * [--to_users=<user_ids>]
299 * : Comma-separated list of specific user IDs to target for password reset.
300 *
301 * [--limit=<number>]
302 * : Maximum number of users to process in a single operation.
303 *
304 * [--paged=<page>]
305 * : Page number for pagination when processing large user sets.
306 *
307 * ## EXAMPLES
308 *
309 * # Check password reset enforcement status for all users.
310 * wp password-reset-enforcement status --to_all
311 *
312 * # Check password reset enforcement status for specific users.
313 * wp password-reset-enforcement status --to_users=1,5,10
314 *
315 * # Check password reset enforcement status for editors and administrators.
316 * wp password-reset-enforcement status --to_roles=editor,administrator
317 *
318 * # Process users in batches with pagination.
319 * wp password-reset-enforcement status --to_all --limit=50 --paged=2
320 *
321 * @param array $args Positional arguments (unused in this command).
322 * @param array $assoc_args Associative arguments containing targeting and configuration options.
323 *
324 * @return void
325 */
326 public function handle_status_request( array $args, array $assoc_args ): void /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
327 $default_args = [
328 'to_all' => false,
329 'to_roles' => [],
330 'to_users' => [],
331 'limit' => null,
332 'paged' => null,
333 ];
334
335 /** @var array{to_all:bool,to_roles:string[],to_users:int[],limit:?int,paged:?int} $assoc_args */
336 $assoc_args = $this->validate_and_sanitize_args(
337 wp_parse_args( $assoc_args, $default_args ),
338 array_keys( $default_args ),
339 );
340
341 $processing = new Processing( $this->container );
342
343 $user_ids = $processing->collect_user_ids( $assoc_args['to_all'], $assoc_args['to_roles'], $assoc_args['to_users'], $assoc_args['limit'], $assoc_args['paged'] );
344
345 $status = $processing->get_password_reset_enforcement_status( $user_ids );
346
347 if ( empty( $status ) ) {
348 WP_CLI::success( 'No users were processed.' );
349 } else {
350 WP_CLI\Utils\format_items( 'table', $status, [ 'user_id', 'user_name', 'needs_password_reset', 'requested_at', 'requested_by', 'with_current_password_allowed' ] );
351 }
352 }
353
354 /**
355 * Validate and sanitize common CLI arguments
356 *
357 * @param array $assoc_args Associative arguments array.
358 * @param array $allowed_args Array of allowed argument names for this command.
359 *
360 * @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.
361 */
362 protected function validate_and_sanitize_args( array $assoc_args, array $allowed_args = [] ): array /* @phpstan-ignore missingType.iterableValue, missingType.iterableValue */ {
363 // Convert boolean arguments.
364 if ( in_array( 'to_all', $allowed_args, true ) ) {
365 $assoc_args['to_all'] = wp_validate_boolean( $assoc_args['to_all'] );
366 }
367
368 if ( in_array( 'with_email', $allowed_args, true ) ) {
369 $assoc_args['with_email'] = wp_validate_boolean( $assoc_args['with_email'] );
370 }
371
372 if ( in_array( 'with_current_password_allowed', $allowed_args, true ) ) {
373 $assoc_args['with_current_password_allowed'] = wp_validate_boolean( $assoc_args['with_current_password_allowed'] );
374 }
375
376 // Convert pagination arguments.
377 if ( in_array( 'limit', $allowed_args, true ) ) {
378 $assoc_args['limit'] = ! is_null( $assoc_args['limit'] ) ? absint( $assoc_args['limit'] ) : null;
379 }
380
381 if ( in_array( 'paged', $allowed_args, true ) ) {
382 $assoc_args['paged'] = ! is_null( $assoc_args['paged'] ) ? absint( $assoc_args['paged'] ) : null;
383 }
384
385 // Convert comma-separated strings to arrays.
386 if ( in_array( 'to_roles', $allowed_args, true ) && is_string( $assoc_args['to_roles'] ) ) {
387 $assoc_args['to_roles'] = array_values(
388 array_filter(
389 array_map(
390 fn ( string $role ): string => Utils\Strings::trim( $role ),
391 explode( ',', $assoc_args['to_roles'] ),
392 ),
393 ),
394 );
395 }
396
397 if ( in_array( 'to_users', $allowed_args, true ) && is_string( $assoc_args['to_users'] ) ) {
398 $assoc_args['to_users'] = array_values(
399 array_filter(
400 array_map(
401 fn ( string $user_id ): string => Utils\Strings::trim( $user_id ),
402 explode( ',', $assoc_args['to_users'] ),
403 ),
404 ),
405 );
406
407 if ( ! empty( $assoc_args['to_users'] ) ) {
408 $users = new Utils\Users( $this->container );
409 $assoc_args['to_users'] = $users->maybe_map_user_logins_to_user_ids( $assoc_args['to_users'] );
410 }
411 }
412
413 // Validate applicability option (only if present in allowed args).
414 if ( in_array( 'applicability', $allowed_args, true ) ) {
415 if ( ! in_array( $assoc_args['applicability'], Processing::APPLICABILITIES, true ) ) {
416 WP_CLI::error( sprintf( 'Invalid applicability value. Must be one of: %s', implode( ', ', Processing::APPLICABILITIES ) ) );
417 }
418 }
419
420 // Validate at least one targeting option is provided.
421 if ( in_array( 'to_all', $allowed_args, true ) || in_array( 'to_roles', $allowed_args, true ) || in_array( 'to_users', $allowed_args, true ) ) {
422 $has_targeting = false;
423
424 if ( in_array( 'to_all', $allowed_args, true ) && $assoc_args['to_all'] ) {
425 $has_targeting = true;
426 }
427
428 if ( in_array( 'to_roles', $allowed_args, true ) && ! empty( $assoc_args['to_roles'] ) ) {
429 $has_targeting = true;
430 }
431
432 if ( in_array( 'to_users', $allowed_args, true ) && ! empty( $assoc_args['to_users'] ) ) {
433 $has_targeting = true;
434 }
435
436 if ( ! $has_targeting ) {
437 WP_CLI::error( 'You must specify at least one targeting option: --to_all, --to_roles, --to_users.' );
438 }
439 }
440
441 // Validate pagination parameters (only if provided).
442 if ( in_array( 'limit', $allowed_args, true ) && ! is_null( $assoc_args['limit'] ) && $assoc_args['limit'] < 1 ) {
443 WP_CLI::error( 'Limit must be a positive integer.' );
444 }
445
446 if ( in_array( 'paged', $allowed_args, true ) && ! is_null( $assoc_args['paged'] ) && $assoc_args['paged'] < 1 ) {
447 WP_CLI::error( 'Page number must be a positive integer.' );
448 }
449
450 // Validate that paged cannot be used without limit.
451 if ( in_array( 'paged', $allowed_args, true ) && in_array( 'limit', $allowed_args, true ) ) {
452 if ( ! is_null( $assoc_args['paged'] ) && is_null( $assoc_args['limit'] ) ) {
453 WP_CLI::error( 'You cannot specify --paged without also specifying --limit.' );
454 }
455 }
456
457 return $assoc_args;
458 }
459 }
460