1 | <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName |
---|
2 | use Automattic\Jetpack\Connection\Client; |
---|
3 | use Automattic\Jetpack\Roles; |
---|
4 | use Automattic\Jetpack\Tracking; |
---|
5 | |
---|
6 | if ( ! class_exists( 'Jetpack_SSO_User_Admin' ) ) : |
---|
7 | require_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php'; |
---|
8 | /** |
---|
9 | * Jetpack sso user admin class. |
---|
10 | */ |
---|
11 | class Jetpack_SSO_User_Admin { |
---|
12 | |
---|
13 | /** |
---|
14 | * Instance of WP_User_Query. |
---|
15 | * |
---|
16 | * @var $user_search |
---|
17 | */ |
---|
18 | private static $user_search = null; |
---|
19 | /** |
---|
20 | * Array of cached invites. |
---|
21 | * |
---|
22 | * @var $cached_invites |
---|
23 | */ |
---|
24 | private static $cached_invites = null; |
---|
25 | |
---|
26 | /** |
---|
27 | * Instance of JetPack Tracking. |
---|
28 | * |
---|
29 | * @var $instance |
---|
30 | */ |
---|
31 | private static $tracking = null; |
---|
32 | |
---|
33 | /** |
---|
34 | * Constructor function. |
---|
35 | */ |
---|
36 | public function __construct() { |
---|
37 | add_action( 'delete_user', array( 'Jetpack_SSO_Helpers', 'delete_connection_for_user' ) ); |
---|
38 | // If the user has no errors on creation, send an invite to WordPress.com. |
---|
39 | add_filter( 'user_profile_update_errors', array( $this, 'send_wpcom_mail_user_invite' ), 10, 3 ); |
---|
40 | add_filter( 'wp_send_new_user_notification_to_user', array( $this, 'should_send_wp_mail_new_user' ) ); |
---|
41 | add_action( 'user_new_form', array( $this, 'render_invitation_email_message' ) ); |
---|
42 | add_action( 'user_new_form', array( $this, 'render_wpcom_invite_checkbox' ), 1 ); |
---|
43 | add_action( 'user_new_form', array( $this, 'render_custom_email_message_form_field' ), 1 ); |
---|
44 | add_action( 'delete_user_form', array( $this, 'render_invitations_notices_for_deleted_users' ) ); |
---|
45 | add_action( 'delete_user', array( $this, 'revoke_user_invite' ) ); |
---|
46 | add_filter( 'manage_users_columns', array( $this, 'jetpack_user_connected_th' ) ); |
---|
47 | add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 ); |
---|
48 | add_action( 'user_row_actions', array( $this, 'jetpack_user_table_row_actions' ), 10, 2 ); |
---|
49 | add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) ); |
---|
50 | add_action( 'admin_post_jetpack_invite_user_to_wpcom', array( $this, 'invite_user_to_wpcom' ) ); |
---|
51 | add_action( 'admin_post_jetpack_revoke_invite_user_to_wpcom', array( $this, 'handle_request_revoke_invite' ) ); |
---|
52 | add_action( 'admin_post_jetpack_resend_invite_user_to_wpcom', array( $this, 'handle_request_resend_invite' ) ); |
---|
53 | add_action( 'admin_print_styles-users.php', array( $this, 'jetpack_user_table_styles' ) ); |
---|
54 | add_action( 'admin_print_styles-user-new.php', array( $this, 'jetpack_user_new_form_styles' ) ); |
---|
55 | add_filter( 'users_list_table_query_args', array( $this, 'set_user_query' ), 100, 1 ); |
---|
56 | |
---|
57 | self::$tracking = new Tracking(); |
---|
58 | } |
---|
59 | |
---|
60 | /** |
---|
61 | * Intercept the arguments for building the table, and create WP_User_Query instance |
---|
62 | * |
---|
63 | * @param array $args The search arguments. |
---|
64 | * |
---|
65 | * @return array |
---|
66 | */ |
---|
67 | public function set_user_query( $args ) { |
---|
68 | self::$user_search = new WP_User_Query( $args ); |
---|
69 | return $args; |
---|
70 | } |
---|
71 | |
---|
72 | /** |
---|
73 | * Revokes WordPress.com invitation. |
---|
74 | * |
---|
75 | * @param int $user_id The user ID. |
---|
76 | */ |
---|
77 | public function revoke_user_invite( $user_id ) { |
---|
78 | try { |
---|
79 | $has_pending_invite = self::has_pending_wpcom_invite( $user_id ); |
---|
80 | |
---|
81 | if ( $has_pending_invite ) { |
---|
82 | $response = self::send_revoke_wpcom_invite( $has_pending_invite ); |
---|
83 | $event = 'sso_user_invite_revoked'; |
---|
84 | |
---|
85 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
---|
86 | self::$tracking->record_user_event( |
---|
87 | $event, |
---|
88 | array( |
---|
89 | 'success' => 'false', |
---|
90 | 'error_message' => 'invalid-revoke-api-error', |
---|
91 | ) |
---|
92 | ); |
---|
93 | return $response; |
---|
94 | } |
---|
95 | |
---|
96 | $body = json_decode( $response['body'] ); |
---|
97 | |
---|
98 | if ( ! $body->deleted ) { |
---|
99 | self::$tracking->record_user_event( |
---|
100 | $event, |
---|
101 | array( |
---|
102 | 'success' => 'false', |
---|
103 | 'error_message' => 'invalid-invite-revoke', |
---|
104 | ) |
---|
105 | ); |
---|
106 | } else { |
---|
107 | self::$tracking->record_user_event( $event, array( 'success' => 'true' ) ); |
---|
108 | } |
---|
109 | |
---|
110 | return $response; |
---|
111 | } |
---|
112 | } catch ( Exception $e ) { |
---|
113 | return false; |
---|
114 | } |
---|
115 | } |
---|
116 | |
---|
117 | /** |
---|
118 | * Renders invitations errors/success messages in users.php. |
---|
119 | */ |
---|
120 | public function handle_invitation_results() { |
---|
121 | $valid_nonce = isset( $_GET['_wpnonce'] ) ? wp_verify_nonce( $_GET['_wpnonce'], 'jetpack-sso-invite-user' ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either. |
---|
122 | |
---|
123 | if ( ! $valid_nonce || ! isset( $_GET['jetpack-sso-invite-user'] ) || ! function_exists( 'wp_admin_notice' ) ) { |
---|
124 | return; |
---|
125 | } |
---|
126 | if ( $_GET['jetpack-sso-invite-user'] === 'success' ) { |
---|
127 | return wp_admin_notice( __( 'User was invited successfully!', 'jetpack' ), array( 'type' => 'success' ) ); |
---|
128 | } |
---|
129 | if ( $_GET['jetpack-sso-invite-user'] === 'reinvited-success' ) { |
---|
130 | return wp_admin_notice( __( 'User was re-invited successfully!', 'jetpack' ), array( 'type' => 'success' ) ); |
---|
131 | } |
---|
132 | |
---|
133 | if ( $_GET['jetpack-sso-invite-user'] === 'successful-revoke' ) { |
---|
134 | return wp_admin_notice( __( 'User invite revoked successfully.', 'jetpack' ), array( 'type' => 'success' ) ); |
---|
135 | } |
---|
136 | |
---|
137 | if ( $_GET['jetpack-sso-invite-user'] === 'failed' && isset( $_GET['jetpack-sso-invite-error'] ) ) { |
---|
138 | switch ( $_GET['jetpack-sso-invite-error'] ) { |
---|
139 | case 'invalid-user': |
---|
140 | return wp_admin_notice( __( 'Tried to invite a user that doesn’t exist.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
141 | case 'invalid-email': |
---|
142 | return wp_admin_notice( __( 'Tried to invite a user that doesn’t have an email address.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
143 | case 'invalid-user-permissions': |
---|
144 | return wp_admin_notice( __( 'You don’t have permission to invite users.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
145 | case 'invalid-user-revoke': |
---|
146 | return wp_admin_notice( __( 'Tried to revoke an invite for a user that doesn’t exist.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
147 | case 'invalid-invite-revoke': |
---|
148 | return wp_admin_notice( __( 'Tried to revoke an invite that doesn’t exist.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
149 | case 'invalid-revoke-permissions': |
---|
150 | return wp_admin_notice( __( 'You don’t have permission to revoke invites.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
151 | case 'empty-invite': |
---|
152 | return wp_admin_notice( __( 'There is no previous invite for this user', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
153 | case 'invalid-invite': |
---|
154 | return wp_admin_notice( __( 'Attempted to send a new invitation to a user using an invite that doesn’t exist.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
155 | case 'error-revoke': |
---|
156 | return wp_admin_notice( __( 'An error has occurred when revoking the invite for the user.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
157 | case 'invalid-revoke-api-error': |
---|
158 | return wp_admin_notice( __( 'An error has occurred when revoking the user invite.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
159 | default: |
---|
160 | return wp_admin_notice( __( 'An error has occurred when inviting the user to the site.', 'jetpack' ), array( 'type' => 'error' ) ); |
---|
161 | } |
---|
162 | } |
---|
163 | } |
---|
164 | |
---|
165 | /** |
---|
166 | * Invites a user to connect to WordPress.com to allow them to log in via SSO. |
---|
167 | */ |
---|
168 | public function invite_user_to_wpcom() { |
---|
169 | check_admin_referer( 'jetpack-sso-invite-user', 'invite_nonce' ); |
---|
170 | $nonce = wp_create_nonce( 'jetpack-sso-invite-user' ); |
---|
171 | $event = 'sso_user_invite_sent'; |
---|
172 | |
---|
173 | if ( ! current_user_can( 'create_users' ) ) { |
---|
174 | $error = 'invalid-user-permissions'; |
---|
175 | $query_params = array( |
---|
176 | 'jetpack-sso-invite-user' => 'failed', |
---|
177 | 'jetpack-sso-invite-error' => $error, |
---|
178 | '_wpnonce' => $nonce, |
---|
179 | ); |
---|
180 | return self::create_error_notice_and_redirect( $query_params ); |
---|
181 | } elseif ( isset( $_GET['user_id'] ) ) { |
---|
182 | $user_id = intval( wp_unslash( $_GET['user_id'] ) ); |
---|
183 | $user = get_user_by( 'id', $user_id ); |
---|
184 | $user_email = $user->user_email; |
---|
185 | |
---|
186 | if ( ! $user || ! $user_email ) { |
---|
187 | $reason = ! $user ? 'invalid-user' : 'invalid-email'; |
---|
188 | $query_params = array( |
---|
189 | 'jetpack-sso-invite-user' => 'failed', |
---|
190 | 'jetpack-sso-invite-error' => $reason, |
---|
191 | '_wpnonce' => $nonce, |
---|
192 | ); |
---|
193 | |
---|
194 | self::$tracking->record_user_event( |
---|
195 | $event, |
---|
196 | array( |
---|
197 | 'success' => 'false', |
---|
198 | 'error_message' => $reason, |
---|
199 | ) |
---|
200 | ); |
---|
201 | return self::create_error_notice_and_redirect( $query_params ); |
---|
202 | } |
---|
203 | |
---|
204 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
205 | $roles = new Roles(); |
---|
206 | $user_role = $roles->translate_user_to_role( $user ); |
---|
207 | |
---|
208 | $url = '/sites/' . $blog_id . '/invites/new'; |
---|
209 | $response = Client::wpcom_json_api_request_as_user( |
---|
210 | $url, |
---|
211 | 'v2', |
---|
212 | array( |
---|
213 | 'method' => 'POST', |
---|
214 | ), |
---|
215 | array( |
---|
216 | 'invitees' => array( |
---|
217 | array( |
---|
218 | 'email_or_username' => $user_email, |
---|
219 | 'role' => $user_role, |
---|
220 | ), |
---|
221 | ), |
---|
222 | ), |
---|
223 | 'wpcom' |
---|
224 | ); |
---|
225 | |
---|
226 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
---|
227 | $error = 'invalid-invite-api-error'; |
---|
228 | $query_params = array( |
---|
229 | 'jetpack-sso-invite-user' => 'failed', |
---|
230 | 'jetpack-sso-invite-error' => $error, |
---|
231 | '_wpnonce' => $nonce, |
---|
232 | ); |
---|
233 | |
---|
234 | self::$tracking->record_user_event( |
---|
235 | $event, |
---|
236 | array( |
---|
237 | 'success' => 'false', |
---|
238 | 'error_message' => $error, |
---|
239 | ) |
---|
240 | ); |
---|
241 | return self::create_error_notice_and_redirect( $query_params ); |
---|
242 | } |
---|
243 | |
---|
244 | // access the first item since we're inviting one user. |
---|
245 | $body = json_decode( $response['body'] )[0]; |
---|
246 | |
---|
247 | $query_params = array( |
---|
248 | 'jetpack-sso-invite-user' => $body->success ? 'success' : 'failed', |
---|
249 | '_wpnonce' => $nonce, |
---|
250 | ); |
---|
251 | |
---|
252 | if ( ! $body->success && $body->errors ) { |
---|
253 | $response_error = array_keys( (array) $body->errors ); |
---|
254 | $query_params['jetpack-sso-invite-error'] = $response_error[0]; |
---|
255 | self::$tracking->record_user_event( |
---|
256 | $event, |
---|
257 | array( |
---|
258 | 'success' => 'false', |
---|
259 | 'error_message' => $response_error[0], |
---|
260 | ) |
---|
261 | ); |
---|
262 | } else { |
---|
263 | self::$tracking->record_user_event( $event, array( 'success' => 'true' ) ); |
---|
264 | } |
---|
265 | |
---|
266 | return self::create_error_notice_and_redirect( $query_params ); |
---|
267 | } else { |
---|
268 | $error = 'invalid-user'; |
---|
269 | $query_params = array( |
---|
270 | 'jetpack-sso-invite-user' => 'failed', |
---|
271 | 'jetpack-sso-invite-error' => $error, |
---|
272 | '_wpnonce' => $nonce, |
---|
273 | ); |
---|
274 | self::$tracking->record_user_event( |
---|
275 | $event, |
---|
276 | array( |
---|
277 | 'success' => 'false', |
---|
278 | 'error_message' => $error, |
---|
279 | ) |
---|
280 | ); |
---|
281 | return self::create_error_notice_and_redirect( $query_params ); |
---|
282 | } |
---|
283 | wp_die(); |
---|
284 | } |
---|
285 | |
---|
286 | /** |
---|
287 | * Revokes a user's invitation to connect to WordPress.com. |
---|
288 | * |
---|
289 | * @param string $invite_id The ID of the invite to revoke. |
---|
290 | */ |
---|
291 | public function send_revoke_wpcom_invite( $invite_id ) { |
---|
292 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
293 | |
---|
294 | $url = '/sites/' . $blog_id . '/invites/delete'; |
---|
295 | return Client::wpcom_json_api_request_as_user( |
---|
296 | $url, |
---|
297 | 'v2', |
---|
298 | array( |
---|
299 | 'method' => 'POST', |
---|
300 | ), |
---|
301 | array( |
---|
302 | 'invite_ids' => array( $invite_id ), |
---|
303 | ), |
---|
304 | 'wpcom' |
---|
305 | ); |
---|
306 | } |
---|
307 | |
---|
308 | /** |
---|
309 | * Handles logic to revoke user invite. |
---|
310 | */ |
---|
311 | public function handle_request_revoke_invite() { |
---|
312 | check_admin_referer( 'jetpack-sso-revoke-user-invite', 'revoke_invite_nonce' ); |
---|
313 | $nonce = wp_create_nonce( 'jetpack-sso-invite-user' ); |
---|
314 | $event = 'sso_user_invite_revoked'; |
---|
315 | if ( ! current_user_can( 'promote_users' ) ) { |
---|
316 | $error = 'invalid-revoke-permissions'; |
---|
317 | $query_params = array( |
---|
318 | 'jetpack-sso-invite-user' => 'failed', |
---|
319 | 'jetpack-sso-invite-error' => $error, |
---|
320 | '_wpnonce' => $nonce, |
---|
321 | ); |
---|
322 | |
---|
323 | return self::create_error_notice_and_redirect( $query_params ); |
---|
324 | } elseif ( isset( $_GET['user_id'] ) ) { |
---|
325 | $user_id = intval( wp_unslash( $_GET['user_id'] ) ); |
---|
326 | $user = get_user_by( 'id', $user_id ); |
---|
327 | if ( ! $user ) { |
---|
328 | $error = 'invalid-user-revoke'; |
---|
329 | $query_params = array( |
---|
330 | 'jetpack-sso-invite-user' => 'failed', |
---|
331 | 'jetpack-sso-invite-error' => $error, |
---|
332 | '_wpnonce' => $nonce, |
---|
333 | ); |
---|
334 | |
---|
335 | self::$tracking->record_user_event( |
---|
336 | $event, |
---|
337 | array( |
---|
338 | 'success' => 'false', |
---|
339 | 'error_message' => $error, |
---|
340 | ) |
---|
341 | ); |
---|
342 | return self::create_error_notice_and_redirect( $query_params ); |
---|
343 | } |
---|
344 | |
---|
345 | if ( ! isset( $_GET['invite_id'] ) ) { |
---|
346 | $error = 'invalid-invite-revoke'; |
---|
347 | $query_params = array( |
---|
348 | 'jetpack-sso-invite-user' => 'failed', |
---|
349 | 'jetpack-sso-invite-error' => $error, |
---|
350 | '_wpnonce' => $nonce, |
---|
351 | ); |
---|
352 | self::$tracking->record_user_event( |
---|
353 | $event, |
---|
354 | array( |
---|
355 | 'success' => 'false', |
---|
356 | 'error_message' => $error, |
---|
357 | ) |
---|
358 | ); |
---|
359 | return self::create_error_notice_and_redirect( $query_params ); |
---|
360 | } |
---|
361 | |
---|
362 | $invite_id = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) ); |
---|
363 | $response = self::send_revoke_wpcom_invite( $invite_id ); |
---|
364 | |
---|
365 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
---|
366 | $error = 'invalid-revoke-api-error'; |
---|
367 | $query_params = array( |
---|
368 | 'jetpack-sso-invite-user' => 'failed', |
---|
369 | 'jetpack-sso-invite-error' => $error, // general error message |
---|
370 | '_wpnonce' => $nonce, |
---|
371 | ); |
---|
372 | self::$tracking->record_user_event( |
---|
373 | $event, |
---|
374 | array( |
---|
375 | 'success' => 'false', |
---|
376 | 'error_message' => $error, |
---|
377 | ) |
---|
378 | ); |
---|
379 | return self::create_error_notice_and_redirect( $query_params ); |
---|
380 | } |
---|
381 | |
---|
382 | $body = json_decode( $response['body'] ); |
---|
383 | $query_params = array( |
---|
384 | 'jetpack-sso-invite-user' => $body->deleted ? 'successful-revoke' : 'failed', |
---|
385 | '_wpnonce' => $nonce, |
---|
386 | ); |
---|
387 | if ( ! $body->deleted ) { // no invite was deleted, probably it does not exist |
---|
388 | $error = 'invalid-invite-revoke'; |
---|
389 | $query_params['jetpack-sso-invite-error'] = $error; |
---|
390 | self::$tracking->record_user_event( |
---|
391 | $event, |
---|
392 | array( |
---|
393 | 'success' => 'false', |
---|
394 | 'error_message' => $error, |
---|
395 | ) |
---|
396 | ); |
---|
397 | } else { |
---|
398 | self::$tracking->record_user_event( $event, array( 'success' => 'true' ) ); |
---|
399 | } |
---|
400 | return self::create_error_notice_and_redirect( $query_params ); |
---|
401 | } else { |
---|
402 | $error = 'invalid-user-revoke'; |
---|
403 | $query_params = array( |
---|
404 | 'jetpack-sso-invite-user' => 'failed', |
---|
405 | 'jetpack-sso-invite-error' => $error, |
---|
406 | '_wpnonce' => $nonce, |
---|
407 | ); |
---|
408 | self::$tracking->record_user_event( |
---|
409 | $event, |
---|
410 | array( |
---|
411 | 'success' => 'false', |
---|
412 | 'error_message' => $error, |
---|
413 | ) |
---|
414 | ); |
---|
415 | return self::create_error_notice_and_redirect( $query_params ); |
---|
416 | } |
---|
417 | |
---|
418 | wp_die(); |
---|
419 | } |
---|
420 | |
---|
421 | /** |
---|
422 | * Handles resend user invite. |
---|
423 | */ |
---|
424 | public function handle_request_resend_invite() { |
---|
425 | check_admin_referer( 'jetpack-sso-resend-user-invite', 'resend_invite_nonce' ); |
---|
426 | $nonce = wp_create_nonce( 'jetpack-sso-invite-user' ); |
---|
427 | $event = 'sso_user_invite_resend'; |
---|
428 | if ( ! current_user_can( 'create_users' ) ) { |
---|
429 | $query_params = array( |
---|
430 | 'jetpack-sso-invite-user' => 'failed', |
---|
431 | 'jetpack-sso-invite-error' => 'invalid-user-permissions', |
---|
432 | '_wpnonce' => $nonce, |
---|
433 | ); |
---|
434 | return self::create_error_notice_and_redirect( $query_params ); |
---|
435 | } elseif ( isset( $_GET['invite_id'] ) ) { |
---|
436 | $invite_slug = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) ); |
---|
437 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
438 | $url = '/sites/' . $blog_id . '/invites/resend'; |
---|
439 | $response = Client::wpcom_json_api_request_as_user( |
---|
440 | $url, |
---|
441 | 'v2', |
---|
442 | array( |
---|
443 | 'method' => 'POST', |
---|
444 | ), |
---|
445 | array( |
---|
446 | 'invite_slug' => $invite_slug, |
---|
447 | ), |
---|
448 | 'wpcom' |
---|
449 | ); |
---|
450 | |
---|
451 | $status_code = wp_remote_retrieve_response_code( $response ); |
---|
452 | |
---|
453 | if ( 200 !== $status_code ) { |
---|
454 | $message_type = $status_code === 404 ? 'invalid-invite' : ''; // empty is the general error message |
---|
455 | $query_params = array( |
---|
456 | 'jetpack-sso-invite-user' => 'failed', |
---|
457 | 'jetpack-sso-invite-error' => $message_type, |
---|
458 | '_wpnonce' => $nonce, |
---|
459 | ); |
---|
460 | self::$tracking->record_user_event( |
---|
461 | $event, |
---|
462 | array( |
---|
463 | 'success' => 'false', |
---|
464 | 'error_message' => $message_type, |
---|
465 | ) |
---|
466 | ); |
---|
467 | return self::create_error_notice_and_redirect( $query_params ); |
---|
468 | } |
---|
469 | |
---|
470 | $body = json_decode( $response['body'] ); |
---|
471 | $invite_response_message = $body->success ? 'reinvited-success' : 'failed'; |
---|
472 | $query_params = array( |
---|
473 | 'jetpack-sso-invite-user' => $invite_response_message, |
---|
474 | '_wpnonce' => $nonce, |
---|
475 | ); |
---|
476 | |
---|
477 | if ( ! $body->success ) { |
---|
478 | self::$tracking->record_user_event( |
---|
479 | $event, |
---|
480 | array( |
---|
481 | 'success' => 'false', |
---|
482 | 'error_message' => $invite_response_message, |
---|
483 | ) |
---|
484 | ); |
---|
485 | } else { |
---|
486 | self::$tracking->record_user_event( $event, array( 'success' => 'true' ) ); |
---|
487 | } |
---|
488 | |
---|
489 | return self::create_error_notice_and_redirect( $query_params ); |
---|
490 | } else { |
---|
491 | $error = 'empty-invite'; |
---|
492 | $query_params = array( |
---|
493 | 'jetpack-sso-invite-user' => 'failed', |
---|
494 | 'jetpack-sso-invite-error' => 'empty-invite', |
---|
495 | '_wpnonce' => $nonce, |
---|
496 | ); |
---|
497 | self::$tracking->record_user_event( |
---|
498 | $event, |
---|
499 | array( |
---|
500 | 'success' => 'false', |
---|
501 | 'error_message' => $error, |
---|
502 | ) |
---|
503 | ); |
---|
504 | return self::create_error_notice_and_redirect( $query_params ); |
---|
505 | } |
---|
506 | } |
---|
507 | |
---|
508 | /** |
---|
509 | * Adds 'Revoke invite' and 'Resend invite' link to user table row actions. |
---|
510 | * Removes 'Reset password' link. |
---|
511 | * |
---|
512 | * @param array $actions - User row actions. |
---|
513 | * @param WP_User $user_object - User object. |
---|
514 | */ |
---|
515 | public function jetpack_user_table_row_actions( $actions, $user_object ) { |
---|
516 | $user_id = $user_object->ID; |
---|
517 | $has_pending_invite = self::has_pending_wpcom_invite( $user_id ); |
---|
518 | |
---|
519 | if ( current_user_can( 'promote_users' ) && $has_pending_invite ) { |
---|
520 | $nonce = wp_create_nonce( 'jetpack-sso-revoke-user-invite' ); |
---|
521 | $actions['sso_revoke_invite'] = sprintf( |
---|
522 | '<a class="jetpack-sso-revoke-invite-action" href="%s">%s</a>', |
---|
523 | add_query_arg( |
---|
524 | array( |
---|
525 | 'action' => 'jetpack_revoke_invite_user_to_wpcom', |
---|
526 | 'user_id' => $user_id, |
---|
527 | 'revoke_invite_nonce' => $nonce, |
---|
528 | 'invite_id' => $has_pending_invite, |
---|
529 | ), |
---|
530 | admin_url( 'admin-post.php' ) |
---|
531 | ), |
---|
532 | esc_html__( 'Revoke invite', 'jetpack' ) |
---|
533 | ); |
---|
534 | } |
---|
535 | if ( current_user_can( 'promote_users' ) && $has_pending_invite ) { |
---|
536 | $nonce = wp_create_nonce( 'jetpack-sso-resend-user-invite' ); |
---|
537 | $actions['sso_resend_invite'] = sprintf( |
---|
538 | '<a class="jetpack-sso-resend-invite-action" href="%s">%s</a>', |
---|
539 | add_query_arg( |
---|
540 | array( |
---|
541 | 'action' => 'jetpack_resend_invite_user_to_wpcom', |
---|
542 | 'user_id' => $user_id, |
---|
543 | 'resend_invite_nonce' => $nonce, |
---|
544 | 'invite_id' => $has_pending_invite, |
---|
545 | ), |
---|
546 | admin_url( 'admin-post.php' ) |
---|
547 | ), |
---|
548 | esc_html__( 'Resend invite', 'jetpack' ) |
---|
549 | ); |
---|
550 | } |
---|
551 | |
---|
552 | unset( $actions['resetpassword'] ); |
---|
553 | |
---|
554 | return $actions; |
---|
555 | } |
---|
556 | |
---|
557 | /** |
---|
558 | * Render the invitation email message. |
---|
559 | */ |
---|
560 | public function render_invitation_email_message() { |
---|
561 | if ( ! function_exists( 'wp_admin_notice' ) ) { |
---|
562 | return; |
---|
563 | } |
---|
564 | $message = wp_kses( |
---|
565 | __( |
---|
566 | 'New users will receive an invite to join WordPress.com, so they can log in securely using <a class="jetpack-sso-admin-create-user-invite-message-link-sso" rel="noopener noreferrer" target="_blank" href="https://jetpack.com/support/sso/">Secure Sign On</a>.', |
---|
567 | 'jetpack' |
---|
568 | ), |
---|
569 | array( |
---|
570 | 'a' => array( |
---|
571 | 'class' => array(), |
---|
572 | 'href' => array(), |
---|
573 | 'rel' => array(), |
---|
574 | 'target' => array(), |
---|
575 | ), |
---|
576 | ) |
---|
577 | ); |
---|
578 | wp_admin_notice( |
---|
579 | $message, |
---|
580 | array( |
---|
581 | 'id' => 'invitation_message', |
---|
582 | 'type' => 'info', |
---|
583 | 'dismissible' => false, |
---|
584 | 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ), |
---|
585 | ) |
---|
586 | ); |
---|
587 | } |
---|
588 | |
---|
589 | /** |
---|
590 | * Render a note that wp.com invites will be automatically revoked. |
---|
591 | */ |
---|
592 | public function render_invitations_notices_for_deleted_users() { |
---|
593 | if ( ! function_exists( 'wp_admin_notice' ) ) { |
---|
594 | return; |
---|
595 | } |
---|
596 | check_admin_referer( 'bulk-users' ); |
---|
597 | |
---|
598 | // When one user is deleted, the param is `user`, when multiple users are deleted, the param is `users`. |
---|
599 | // We start with `users` and fallback to `user`. |
---|
600 | $user_id = isset( $_GET['user'] ) ? intval( wp_unslash( $_GET['user'] ) ) : null; |
---|
601 | $user_ids = isset( $_GET['users'] ) ? array_map( 'intval', wp_unslash( $_GET['users'] ) ) : array( $user_id ); |
---|
602 | |
---|
603 | $users_with_invites = array_filter( |
---|
604 | $user_ids, |
---|
605 | function ( $user_id ) { |
---|
606 | return $user_id !== null && self::has_pending_wpcom_invite( $user_id ); |
---|
607 | } |
---|
608 | ); |
---|
609 | |
---|
610 | $users_with_invites = array_map( |
---|
611 | function ( $user_id ) { |
---|
612 | $user = get_user_by( 'id', $user_id ); |
---|
613 | return $user->user_login; |
---|
614 | }, |
---|
615 | $users_with_invites |
---|
616 | ); |
---|
617 | |
---|
618 | $invites_count = count( $users_with_invites ); |
---|
619 | if ( $invites_count > 0 ) { |
---|
620 | $users_with_invites = implode( ', ', $users_with_invites ); |
---|
621 | $message = wp_kses( |
---|
622 | sprintf( |
---|
623 | /* translators: %s is a comma-separated list of user logins. */ |
---|
624 | _n( |
---|
625 | 'WordPress.com invitation will be automatically revoked for user: <strong>%s</strong>.', |
---|
626 | 'WordPress.com invitations will be automatically revoked for users: <strong>%s</strong>.', |
---|
627 | $invites_count, |
---|
628 | 'jetpack' |
---|
629 | ), |
---|
630 | $users_with_invites |
---|
631 | ), |
---|
632 | array( 'strong' => true ) |
---|
633 | ); |
---|
634 | wp_admin_notice( |
---|
635 | $message, |
---|
636 | array( |
---|
637 | 'id' => 'invitation_message', |
---|
638 | 'type' => 'info', |
---|
639 | 'dismissible' => false, |
---|
640 | 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ), |
---|
641 | ) |
---|
642 | ); |
---|
643 | } |
---|
644 | } |
---|
645 | |
---|
646 | /** |
---|
647 | * Render WordPress.com invite checkbox for new user registration. |
---|
648 | * |
---|
649 | * @param string $type The type of new user form the hook follows. |
---|
650 | */ |
---|
651 | public function render_wpcom_invite_checkbox( $type ) { |
---|
652 | if ( $type === 'add-new-user' ) { |
---|
653 | ?> |
---|
654 | <table class="form-table"> |
---|
655 | <tr class="form-field"> |
---|
656 | <th scope="row"> |
---|
657 | <label for="invite_user_wpcom"><?php esc_html_e( 'Invite user:', 'jetpack' ); ?></label> |
---|
658 | </th> |
---|
659 | <td> |
---|
660 | <fieldset> |
---|
661 | <legend class="screen-reader-text"> |
---|
662 | <span><?php esc_html_e( 'Invite user', 'jetpack' ); ?></span> |
---|
663 | </legend> |
---|
664 | <label for="invite_user_wpcom"> |
---|
665 | <input name="invite_user_wpcom" type="checkbox" id="invite_user_wpcom" checked> |
---|
666 | <?php esc_html_e( 'Invite user to WordPress.com', 'jetpack' ); ?> |
---|
667 | </label> |
---|
668 | </fieldset> |
---|
669 | </td> |
---|
670 | </tr> |
---|
671 | </table> |
---|
672 | <?php |
---|
673 | } |
---|
674 | } |
---|
675 | |
---|
676 | /** |
---|
677 | * Render the custom email message form field for new user registration. |
---|
678 | * |
---|
679 | * @param string $type The type of new user form the hook follows. |
---|
680 | */ |
---|
681 | public function render_custom_email_message_form_field( $type ) { |
---|
682 | if ( $type === 'add-new-user' ) { |
---|
683 | $valid_nonce = isset( $_POST['_wpnonce_create-user'] ) ? wp_verify_nonce( $_POST['_wpnonce_create-user'], 'create-user' ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either. |
---|
684 | $custom_email_message = ( $valid_nonce && isset( $_POST['custom_email_message'] ) ) ? sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) : ''; |
---|
685 | ?> |
---|
686 | <table class="form-table"> |
---|
687 | <tr class="form-field"> |
---|
688 | <th scope="row"> |
---|
689 | <label for="custom_email_message"><?php esc_html_e( 'Custom Message', 'jetpack' ); ?></label> |
---|
690 | </th> |
---|
691 | <td> |
---|
692 | <label for="custom_email_message"> |
---|
693 | <textarea aria-describedby="custom_email_message_description" rows="3" maxlength="500" id="custom_email_message" name="custom_email_message"><?php echo esc_html( $custom_email_message ); ?></textarea> |
---|
694 | <p id="custom_email_message_description"> |
---|
695 | <?php |
---|
696 | esc_html_e( 'This user will be invited to WordPress.com. You can include a personalized welcome message with the invitation.', 'jetpack' ); |
---|
697 | ?> |
---|
698 | </label> |
---|
699 | </td> |
---|
700 | </tr> |
---|
701 | </table> |
---|
702 | <?php |
---|
703 | } |
---|
704 | } |
---|
705 | |
---|
706 | /** |
---|
707 | * Conditionally disable the core invitation email. |
---|
708 | * It should be sent when SSO is disabled or when admins opt-out of WordPress.com invites intentionally. |
---|
709 | * |
---|
710 | * @return boolean Indicating if the core invitation main should be sent. |
---|
711 | */ |
---|
712 | public function should_send_wp_mail_new_user() { |
---|
713 | return empty( $_POST['invite_user_wpcom'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing |
---|
714 | } |
---|
715 | |
---|
716 | /** |
---|
717 | * Send user invitation to WordPress.com if user has no errors. |
---|
718 | * |
---|
719 | * @param WP_Error $errors The WP_Error object. |
---|
720 | * @param bool $update Whether the user is being updated or not. |
---|
721 | * @param stdClass $user The User object about to be created. |
---|
722 | * @return WP_Error The modified or not WP_Error object. |
---|
723 | */ |
---|
724 | public function send_wpcom_mail_user_invite( $errors, $update, $user ) { |
---|
725 | if ( ! $update ) { |
---|
726 | $valid_nonce = isset( $_POST['_wpnonce_create-user'] ) ? wp_verify_nonce( $_POST['_wpnonce_create-user'], 'create-user' ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either. |
---|
727 | |
---|
728 | if ( $this->should_send_wp_mail_new_user() ) { |
---|
729 | return $errors; |
---|
730 | } |
---|
731 | |
---|
732 | if ( $valid_nonce && ! empty( $_POST['custom_email_message'] ) && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) ) > 500 ) { |
---|
733 | $errors->add( 'custom_email_message', __( '<strong>Error</strong>: The custom message is too long. Please keep it under 500 characters.', 'jetpack' ) ); |
---|
734 | } |
---|
735 | |
---|
736 | if ( $errors->has_errors() ) { |
---|
737 | return $errors; |
---|
738 | } |
---|
739 | |
---|
740 | $email = $user->user_email; |
---|
741 | $role = $user->role; |
---|
742 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
743 | $url = '/sites/' . $blog_id . '/invites/new'; |
---|
744 | |
---|
745 | $new_user_request = array( |
---|
746 | 'email_or_username' => $email, |
---|
747 | 'role' => $role, |
---|
748 | ); |
---|
749 | |
---|
750 | if ( $valid_nonce && isset( $_POST['custom_email_message'] ) && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) > 0 ) ) { |
---|
751 | $new_user_request['message'] = sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ); |
---|
752 | } |
---|
753 | |
---|
754 | $response = Client::wpcom_json_api_request_as_user( |
---|
755 | $url, |
---|
756 | '2', // Api version |
---|
757 | array( |
---|
758 | 'method' => 'POST', |
---|
759 | ), |
---|
760 | array( |
---|
761 | 'invitees' => array( $new_user_request ), |
---|
762 | ) |
---|
763 | ); |
---|
764 | |
---|
765 | $event = 'sso_new_user_invite_sent'; |
---|
766 | $custom_message_sent = isset( $new_user_request['message'] ) ? 'true' : 'false'; |
---|
767 | |
---|
768 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
---|
769 | $errors->add( 'invitation_not_sent', __( '<strong>Error</strong>: The user invitation email could not be sent, the user account was not created.', 'jetpack' ) ); |
---|
770 | self::$tracking->record_user_event( |
---|
771 | $event, |
---|
772 | array( |
---|
773 | 'success' => 'false', |
---|
774 | ) |
---|
775 | ); |
---|
776 | } else { |
---|
777 | self::$tracking->record_user_event( |
---|
778 | $event, |
---|
779 | array( |
---|
780 | 'success' => 'true', |
---|
781 | 'custom_message_sent' => $custom_message_sent, |
---|
782 | ) |
---|
783 | ); |
---|
784 | } |
---|
785 | } |
---|
786 | |
---|
787 | return $errors; |
---|
788 | } |
---|
789 | |
---|
790 | /** |
---|
791 | * Adds a column in the user admin table to display user connection status and actions. |
---|
792 | * |
---|
793 | * @param array $columns User list table columns. |
---|
794 | * |
---|
795 | * @return array |
---|
796 | */ |
---|
797 | public function jetpack_user_connected_th( $columns ) { |
---|
798 | $columns['user_jetpack'] = sprintf( |
---|
799 | '<span title="%1$s">%2$s [?]</span>', |
---|
800 | esc_attr__( 'Jetpack SSO allows a seamless and secure experience on WordPress.com. Join millions of WordPress users who trust us to keep their accounts safe.', 'jetpack' ), |
---|
801 | esc_html__( 'SSO Status', 'jetpack' ) |
---|
802 | ); |
---|
803 | return $columns; |
---|
804 | } |
---|
805 | |
---|
806 | /** |
---|
807 | * Executed when our WP_User_Query instance is set, and we don't have cached invites. |
---|
808 | * This function uses the user emails and the 'are-users-invited' endpoint to build the cache. |
---|
809 | * |
---|
810 | * @return void |
---|
811 | */ |
---|
812 | private static function rebuild_invite_cache() { |
---|
813 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
814 | |
---|
815 | if ( self::$cached_invites === null && self::$user_search !== null ) { |
---|
816 | |
---|
817 | self::$cached_invites = array(); |
---|
818 | |
---|
819 | $results = self::$user_search->get_results(); |
---|
820 | |
---|
821 | $user_emails = array_reduce( |
---|
822 | $results, |
---|
823 | function ( $current, $item ) { |
---|
824 | if ( ! Jetpack::connection()->is_user_connected( $item->ID ) ) { |
---|
825 | $current[] = rawurlencode( $item->user_email ); |
---|
826 | } else { |
---|
827 | self::$cached_invites[] = array( |
---|
828 | 'email_or_username' => $item->user_email, |
---|
829 | 'invited' => false, |
---|
830 | 'invite_code' => '', |
---|
831 | ); |
---|
832 | } |
---|
833 | return $current; |
---|
834 | }, |
---|
835 | array() |
---|
836 | ); |
---|
837 | |
---|
838 | if ( ! empty( $user_emails ) ) { |
---|
839 | $url = '/sites/' . $blog_id . '/invites/are-users-invited'; |
---|
840 | |
---|
841 | $response = Client::wpcom_json_api_request_as_user( |
---|
842 | $url, |
---|
843 | 'v2', |
---|
844 | array( |
---|
845 | 'method' => 'POST', |
---|
846 | ), |
---|
847 | array( 'users' => $user_emails ), |
---|
848 | 'wpcom' |
---|
849 | ); |
---|
850 | |
---|
851 | if ( 200 === wp_remote_retrieve_response_code( $response ) ) { |
---|
852 | $body = json_decode( $response['body'], true ); |
---|
853 | |
---|
854 | // ensure array_merge happens with the right parameters |
---|
855 | if ( empty( $body ) ) { |
---|
856 | $body = array(); |
---|
857 | } |
---|
858 | |
---|
859 | self::$cached_invites = array_merge( self::$cached_invites, $body ); |
---|
860 | } |
---|
861 | } |
---|
862 | } |
---|
863 | } |
---|
864 | |
---|
865 | /** |
---|
866 | * Check if there is cached invite for a user email. |
---|
867 | * |
---|
868 | * @access private |
---|
869 | * @static |
---|
870 | * |
---|
871 | * @param string $email The user email. |
---|
872 | * |
---|
873 | * @return array|void Returns the cached invite if found. |
---|
874 | */ |
---|
875 | public static function get_pending_cached_wpcom_invite( $email ) { |
---|
876 | if ( self::$cached_invites === null ) { |
---|
877 | self::rebuild_invite_cache(); |
---|
878 | } |
---|
879 | |
---|
880 | if ( ! empty( self::$cached_invites ) ) { |
---|
881 | $index = array_search( $email, array_column( self::$cached_invites, 'email_or_username' ), true ); |
---|
882 | if ( $index !== false ) { |
---|
883 | return self::$cached_invites[ $index ]; |
---|
884 | } |
---|
885 | } |
---|
886 | } |
---|
887 | |
---|
888 | /** |
---|
889 | * Check if a given user is invited to the site. |
---|
890 | * |
---|
891 | * @access private |
---|
892 | * @static |
---|
893 | * @param int $user_id The user ID. |
---|
894 | * |
---|
895 | * @return {false|string} returns the user invite code if the user is invited, false otherwise. |
---|
896 | */ |
---|
897 | private static function has_pending_wpcom_invite( $user_id ) { |
---|
898 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
899 | $user = get_user_by( 'id', $user_id ); |
---|
900 | $cached_invite = self::get_pending_cached_wpcom_invite( $user->user_email ); |
---|
901 | |
---|
902 | if ( $cached_invite ) { |
---|
903 | return $cached_invite['invite_code']; |
---|
904 | } |
---|
905 | |
---|
906 | $url = '/sites/' . $blog_id . '/invites/is-invited'; |
---|
907 | $url = add_query_arg( |
---|
908 | array( |
---|
909 | 'email_or_username' => rawurlencode( $user->user_email ), |
---|
910 | ), |
---|
911 | $url |
---|
912 | ); |
---|
913 | $response = Client::wpcom_json_api_request_as_user( |
---|
914 | $url, |
---|
915 | 'v2', |
---|
916 | array(), |
---|
917 | null, |
---|
918 | 'wpcom' |
---|
919 | ); |
---|
920 | |
---|
921 | if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { |
---|
922 | return false; |
---|
923 | } |
---|
924 | |
---|
925 | return json_decode( $response['body'], true )['invite_code']; |
---|
926 | } |
---|
927 | |
---|
928 | /** |
---|
929 | * Show Jetpack SSO user connection status. |
---|
930 | * |
---|
931 | * @param string $val HTML for the column. |
---|
932 | * @param string $col User list table column. |
---|
933 | * @param int $user_id User ID. |
---|
934 | * |
---|
935 | * @return string |
---|
936 | */ |
---|
937 | public function jetpack_show_connection_status( $val, $col, $user_id ) { |
---|
938 | if ( 'user_jetpack' === $col ) { |
---|
939 | if ( Jetpack::connection()->is_user_connected( $user_id ) ) { |
---|
940 | $connection_html = sprintf( |
---|
941 | '<span title="%1$s" class="jetpack-sso-invitation">%2$s</span>', |
---|
942 | esc_attr__( 'This user is connected and can log-in to this site.', 'jetpack' ), |
---|
943 | esc_html__( 'Connected', 'jetpack' ) |
---|
944 | ); |
---|
945 | return $connection_html; |
---|
946 | } else { |
---|
947 | $has_pending_invite = self::has_pending_wpcom_invite( $user_id ); |
---|
948 | if ( $has_pending_invite ) { |
---|
949 | $connection_html = sprintf( |
---|
950 | '<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>', |
---|
951 | esc_attr__( 'This user didn’t accept the invitation to join this site yet.', 'jetpack' ), |
---|
952 | esc_html__( 'Pending invite', 'jetpack' ) |
---|
953 | ); |
---|
954 | return $connection_html; |
---|
955 | } |
---|
956 | $nonce = wp_create_nonce( 'jetpack-sso-invite-user' ); |
---|
957 | $connection_html = sprintf( |
---|
958 | // Using formmethod and formaction because we can't nest forms and have to submit using the main form. |
---|
959 | '<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a><span title="%3$s" class="sso-disconnected-user-icon dashicons dashicons-warning"></span>', |
---|
960 | add_query_arg( |
---|
961 | array( |
---|
962 | 'user_id' => $user_id, |
---|
963 | 'invite_nonce' => $nonce, |
---|
964 | 'action' => 'jetpack_invite_user_to_wpcom', |
---|
965 | ), |
---|
966 | admin_url( 'admin-post.php' ) |
---|
967 | ), |
---|
968 | esc_html__( 'Send invite', 'jetpack' ), |
---|
969 | esc_attr__( 'This user doesn’t have an SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack' ) |
---|
970 | ); |
---|
971 | return $connection_html; |
---|
972 | } |
---|
973 | } |
---|
974 | } |
---|
975 | |
---|
976 | /** |
---|
977 | * Creates error notices and redirects the user to the previous page. |
---|
978 | * |
---|
979 | * @param array $query_params - query parameters added to redirection URL. |
---|
980 | */ |
---|
981 | public function create_error_notice_and_redirect( $query_params ) { |
---|
982 | $ref = wp_get_referer(); |
---|
983 | if ( empty( $ref ) ) { |
---|
984 | $ref = network_admin_url( 'users.php' ); |
---|
985 | } |
---|
986 | |
---|
987 | $url = add_query_arg( |
---|
988 | $query_params, |
---|
989 | $ref |
---|
990 | ); |
---|
991 | return wp_safe_redirect( $url ); |
---|
992 | } |
---|
993 | |
---|
994 | /** |
---|
995 | * Style the Jetpack user rows and columns. |
---|
996 | */ |
---|
997 | public function jetpack_user_table_styles() { |
---|
998 | ?> |
---|
999 | <style> |
---|
1000 | #the-list tr:has(.sso-disconnected-user) { |
---|
1001 | background: #F5F1E1; |
---|
1002 | } |
---|
1003 | #the-list tr:has(.sso-pending-invite) { |
---|
1004 | background: #E9F0F5; |
---|
1005 | } |
---|
1006 | .fixed .column-user_jetpack { |
---|
1007 | width: 100px; |
---|
1008 | } |
---|
1009 | .jetpack-sso-invitation { |
---|
1010 | background: none; |
---|
1011 | border: none; |
---|
1012 | color: #50575e; |
---|
1013 | padding: 0; |
---|
1014 | text-align: unset; |
---|
1015 | } |
---|
1016 | .jetpack-sso-invitation.sso-disconnected-user { |
---|
1017 | color: #0073aa; |
---|
1018 | cursor: pointer; |
---|
1019 | text-decoration: underline; |
---|
1020 | } |
---|
1021 | .jetpack-sso-invitation.sso-disconnected-user:hover, |
---|
1022 | .jetpack-sso-invitation.sso-disconnected-user:focus, |
---|
1023 | .jetpack-sso-invitation.sso-disconnected-user:active { |
---|
1024 | color: #0096dd; |
---|
1025 | } |
---|
1026 | |
---|
1027 | .sso-disconnected-user-icon { |
---|
1028 | margin-left: 4px; |
---|
1029 | cursor: pointer; |
---|
1030 | background: gray; |
---|
1031 | border-radius: 10px; |
---|
1032 | } |
---|
1033 | |
---|
1034 | .sso-disconnected-user-icon.dashicons { |
---|
1035 | font-size: 1rem; |
---|
1036 | height: 1rem; |
---|
1037 | width: 1rem; |
---|
1038 | background-color: #9D6E00; |
---|
1039 | color: #F5F1E1; |
---|
1040 | } |
---|
1041 | |
---|
1042 | </style> |
---|
1043 | <?php |
---|
1044 | } |
---|
1045 | |
---|
1046 | /** |
---|
1047 | * Enqueue style for the Jetpack user new form. |
---|
1048 | */ |
---|
1049 | public function jetpack_user_new_form_styles() { |
---|
1050 | // Enqueue the CSS for the admin create user page. |
---|
1051 | wp_enqueue_style( 'jetpack-sso-admin-create-user', plugins_url( 'modules/sso/jetpack-sso-admin-create-user.css', JETPACK__PLUGIN_FILE ), array(), time() ); |
---|
1052 | } |
---|
1053 | } |
---|
1054 | endif; |
---|