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 / class-processing.php

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

288 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Processes password reset enforcement for users
4 *
5 * @package Teydea_Studio\Password_Reset
6 */
7
8 namespace Teydea_Studio\Password_Reset;
9
10 use Teydea_Studio\Password_Reset\Dependencies\Utils;
11 use WP_User;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // @codeCoverageIgnore
15 }
16
17 /**
18 * The "Processing" class
19 */
20 class Processing {
21 /**
22 * Applicability: log the user out immediately
23 *
24 * @var string
25 */
26 const APPLICABILITY_IMMEDIATELY = 'immediately';
27
28 /**
29 * Applicability: enforce the reset once the current session expires
30 *
31 * @var string
32 */
33 const APPLICABILITY_AFTER_SESSION_EXPIRY = 'after_session_expiry';
34
35 /**
36 * Allowed applicability values
37 *
38 * @var string[]
39 */
40 const APPLICABILITIES = [
41 self::APPLICABILITY_IMMEDIATELY,
42 self::APPLICABILITY_AFTER_SESSION_EXPIRY,
43 ];
44
45 /**
46 * Container instance
47 *
48 * @var Utils\Container
49 */
50 protected object $container;
51
52 /**
53 * Constructor
54 *
55 * @param Utils\Container $container Container instance.
56 */
57 public function __construct( object $container ) {
58 $this->container = $container;
59 }
60
61 /**
62 * Build the default user factory
63 *
64 * @return callable(WP_User): User Factory turning a WP_User into the plugin's User instance.
65 */
66 protected function get_default_user_factory(): callable {
67 return function ( WP_User $wp_user ): User {
68 return new User( $this->container, $wp_user ); // @codeCoverageIgnore
69 };
70 }
71
72 /**
73 * Collect user IDs based on given criteria
74 *
75 * @param bool $to_all Whether to apply to all users.
76 * @param string[] $to_roles Array of roles to apply to.
77 * @param int[]|string[] $to_users Array of user IDs to apply to.
78 * @param ?int $limit Number of users to process per batch, or null.
79 * @param ?int $paged Page number for pagination, or null.
80 * @param ?Utils\Users $users Users utility instance for dependency injection (optional, creates new if not provided).
81 *
82 * @return int[] Array of user IDs collected.
83 */
84 public function collect_user_ids( bool $to_all, array $to_roles, array $to_users, ?int $limit = null, ?int $paged = null, ?object $users = null ): array {
85 if ( null === $users ) {
86 $users = new Utils\Users( $this->container ); // @codeCoverageIgnore
87 }
88
89 if ( true === $to_all ) {
90 $to_roles = [];
91 $to_users = [];
92 }
93
94 // Map user logins to user IDs, as the user query requires user IDs.
95 if ( ! empty( $to_users ) ) {
96 $to_users = $users->maybe_map_user_logins_to_user_ids( $to_users );
97 }
98
99 return $users->get_users_batch( $to_all, $to_roles, $to_users, $limit, $paged, true );
100 }
101
102 /**
103 * Clear password reset enforcement for users
104 *
105 * @param int[] $user_ids Array of user IDs to process.
106 * @param (callable(WP_User): User)|null $user_factory Optional user factory callable for dependency injection. If null, uses default User instantiation.
107 *
108 * @return void
109 */
110 public function clear_password_reset_enforcement( array $user_ids, ?callable $user_factory = null ): void {
111 $user_factory ??= $this->get_default_user_factory();
112
113 foreach ( $user_ids as $user_id ) {
114 $wp_user = get_user_by( 'ID', $user_id );
115
116 if ( ! $wp_user instanceof WP_User ) {
117 continue;
118 }
119
120 $user = $user_factory( $wp_user );
121 $user->remove_password_reset_enforcement();
122 }
123 }
124
125 /**
126 * Force password reset for users
127 *
128 * @param int[] $user_ids Array of user IDs to process.
129 * @param string $applicability One of the self::APPLICABILITY_* values.
130 * @param bool $with_email Whether to send email notification.
131 * @param bool $with_current_password_allowed Whether to allow current password.
132 * @param int|string $requestor Current user ID or identifier performing the action.
133 * @param (callable(WP_User): User)|null $user_factory Optional user factory callable for dependency injection. If null, uses default User instantiation.
134 *
135 * @return void
136 */
137 public function force_password_reset( array $user_ids, string $applicability, bool $with_email, bool $with_current_password_allowed, $requestor = '', ?callable $user_factory = null ): void {
138 $user_factory ??= $this->get_default_user_factory();
139
140 foreach ( $user_ids as $user_id ) {
141 $wp_user = get_user_by( 'ID', $user_id );
142
143 if ( ! $wp_user instanceof WP_User ) {
144 continue;
145 }
146
147 $user = $user_factory( $wp_user );
148
149 if ( self::APPLICABILITY_IMMEDIATELY === $applicability ) {
150 $user->logout_everywhere();
151 }
152
153 if ( true === $with_email ) {
154 $user->send_email_with_link();
155 }
156
157 $user->force_password_reset( $requestor, $with_current_password_allowed );
158 }
159 }
160
161 /**
162 * Get the password reset enforcement status for users
163 *
164 * @param int[] $user_ids Array of user IDs to process.
165 * @param (callable(WP_User): User)|null $user_factory Optional user factory callable for dependency injection. If null, uses default User instantiation.
166 *
167 * @return array<int,array{user_id:int,needs_password_reset:string,requested_at:string,requested_by:string,with_current_password_allowed:string}> Array containing status information about the password reset enforcement for users.
168 */
169 public function get_password_reset_enforcement_status( array $user_ids, ?callable $user_factory = null ): array {
170 $user_factory ??= $this->get_default_user_factory();
171 $results = [];
172
173 foreach ( $user_ids as $user_id ) {
174 $wp_user = get_user_by( 'ID', $user_id );
175
176 if ( ! $wp_user instanceof WP_User ) {
177 continue;
178 }
179
180 $user = $user_factory( $wp_user );
181 $data = $user->get_password_reset_request_data();
182
183 $result = [
184 'user_id' => $user_id,
185 'user_name' => $user->get_user_login(),
186 'needs_password_reset' => 'no',
187 'requested_at' => '-',
188 'requested_by' => '-',
189 'with_current_password_allowed' => '-',
190 ];
191
192 if ( null !== $data ) {
193 $result['needs_password_reset'] = 'yes';
194
195 $result['requested_at'] = sprintf(
196 '%1$s (%2$s ago)',
197 Utils\Date_Time::get_i18n_datetime_string( $data['requested_at'] ),
198 human_time_diff( $data['requested_at'] ),
199 );
200
201 $result['requested_by'] = $user->get_requestor_display_name( $data['requested_by'] );
202 $result['with_current_password_allowed'] = Utils\Type::ensure_bool( $data['with_current_password_allowed'] ) ? 'yes' : 'no';
203 }
204
205 $results[] = $result;
206 }
207
208 return $results;
209 }
210
211 /**
212 * List users who have a pending password reset request
213 *
214 * Resolves affected users by querying for the existence of the enforcement
215 * request user meta — the feature's data store.
216 *
217 * @param ?int $limit Number of users to process per batch, or null for no limit.
218 * @param ?int $paged Page number for pagination (1-based), or null for no pagination.
219 * @param (callable(WP_User): User)|null $user_factory Optional user factory callable for dependency injection. If null, uses default User instantiation.
220 *
221 * @return array<int,array{user_id:int,user_name:string,requested_at:string,requested_by:string,with_current_password_allowed:string}> Array containing information about users with pending password reset requests.
222 */
223 public function list_users_with_enforced_password_reset( ?int $limit = null, ?int $paged = null, ?callable $user_factory = null ): array {
224 $user_factory ??= $this->get_default_user_factory();
225 $results = [];
226
227 // "blog_id => 0" keeps the network-wide scope of the enforcement request meta.
228 $args = [
229 'meta_key' => ( new User( $this->container ) )->get_prefixed_meta_key( User::USER_META_KEY__REQUEST ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- the enforcement request meta is the feature's data store; there's no other way to resolve affected users.
230 'meta_compare' => 'EXISTS',
231 'fields' => 'ID',
232 'orderby' => 'ID',
233 'order' => 'ASC',
234 'count_total' => false,
235 'blog_id' => 0,
236 ];
237
238 if ( null !== $limit ) {
239 $args['number'] = $limit;
240
241 if ( null !== $paged ) {
242 $args['paged'] = $paged;
243 }
244 }
245
246 $user_ids = Utils\Type::ensure_array_of_ints( get_users( $args ) );
247
248 foreach ( $user_ids as $user_id ) {
249 $wp_user = get_user_by( 'ID', $user_id );
250
251 if ( ! $wp_user instanceof WP_User ) {
252 continue;
253 }
254
255 $user = $user_factory( $wp_user );
256
257 if ( null === $user->get_user_login() ) {
258 continue;
259 }
260
261 $data = $user->get_password_reset_request_data();
262
263 $result = [
264 'user_id' => $user_id,
265 'user_name' => $user->get_user_login(),
266 'requested_at' => '-',
267 'requested_by' => '-',
268 'with_current_password_allowed' => '-',
269 ];
270
271 if ( null !== $data ) {
272 $result['requested_at'] = sprintf(
273 '%1$s (%2$s ago)',
274 Utils\Date_Time::get_i18n_datetime_string( $data['requested_at'] ),
275 human_time_diff( $data['requested_at'] ),
276 );
277
278 $result['requested_by'] = $user->get_requestor_display_name( $data['requested_by'] );
279 $result['with_current_password_allowed'] = Utils\Type::ensure_bool( $data['with_current_password_allowed'] ) ? 'yes' : 'no';
280 }
281
282 $results[] = $result;
283 }
284
285 return $results;
286 }
287 }
288