PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.4.5
Jetpack – WP Security, Backup, Speed, & Growth v13.4.5
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / sso / class.jetpack-sso-user-admin.php
jetpack / modules / sso Last commit date
class-jetpack-force-2fa.php 2 years ago class.jetpack-sso-helpers.php 2 years ago class.jetpack-sso-notices.php 4 years ago class.jetpack-sso-user-admin.php 2 years ago jetpack-sso-admin-create-user-rtl.css 2 years ago jetpack-sso-admin-create-user-rtl.min.css 2 years ago jetpack-sso-admin-create-user.css 2 years ago jetpack-sso-admin-create-user.js 2 years ago jetpack-sso-admin-create-user.min.css 2 years ago jetpack-sso-login-rtl.css 4 years ago jetpack-sso-login-rtl.min.css 4 years ago jetpack-sso-login.css 4 years ago jetpack-sso-login.js 2 years ago jetpack-sso-login.min.css 4 years ago jetpack-sso-users.js 2 years ago
class.jetpack-sso-user-admin.php
1258 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 use Automattic\Jetpack\Assets;
3 use Automattic\Jetpack\Connection\Client;
4 use Automattic\Jetpack\Connection\Manager;
5 use Automattic\Jetpack\Roles;
6 use Automattic\Jetpack\Status\Host;
7 use Automattic\Jetpack\Tracking;
8
9 if ( ! class_exists( 'Jetpack_SSO_User_Admin' ) ) :
10 require_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php';
11 /**
12 * Jetpack sso user admin class.
13 */
14 class Jetpack_SSO_User_Admin {
15
16 /**
17 * Instance of WP_User_Query.
18 *
19 * @var $user_search
20 */
21 private static $user_search = null;
22 /**
23 * Array of cached invites.
24 *
25 * @var $cached_invites
26 */
27 private static $cached_invites = null;
28
29 /**
30 * Instance of Jetpack Tracking.
31 *
32 * @var $instance
33 */
34 private static $tracking = null;
35
36 /**
37 * Constructor function.
38 */
39 public function __construct() {
40 add_action( 'delete_user', array( 'Jetpack_SSO_Helpers', 'delete_connection_for_user' ) );
41 // If the user has no errors on creation, send an invite to WordPress.com.
42 add_filter( 'user_profile_update_errors', array( $this, 'send_wpcom_mail_user_invite' ), 10, 3 );
43 add_filter( 'wp_send_new_user_notification_to_user', array( $this, 'should_send_wp_mail_new_user' ) );
44 add_action( 'user_new_form', array( $this, 'render_invitation_email_message' ) );
45 add_action( 'user_new_form', array( $this, 'render_wpcom_invite_checkbox' ), 1 );
46 add_action( 'user_new_form', array( $this, 'render_wpcom_external_user_checkbox' ), 1 );
47 add_action( 'user_new_form', array( $this, 'render_custom_email_message_form_field' ), 1 );
48 add_action( 'delete_user_form', array( $this, 'render_invitations_notices_for_deleted_users' ) );
49 add_action( 'delete_user', array( $this, 'revoke_user_invite' ) );
50 add_filter( 'manage_users_columns', array( $this, 'jetpack_user_connected_th' ) );
51 add_action( 'manage_users_custom_column', array( $this, 'jetpack_show_connection_status' ), 10, 3 );
52 add_action( 'user_row_actions', array( $this, 'jetpack_user_table_row_actions' ), 10, 2 );
53 add_action( 'admin_notices', array( $this, 'handle_invitation_results' ) );
54 add_action( 'admin_post_jetpack_invite_user_to_wpcom', array( $this, 'invite_user_to_wpcom' ) );
55 add_action( 'admin_post_jetpack_revoke_invite_user_to_wpcom', array( $this, 'handle_request_revoke_invite' ) );
56 add_action( 'admin_post_jetpack_resend_invite_user_to_wpcom', array( $this, 'handle_request_resend_invite' ) );
57 add_action( 'admin_print_styles-users.php', array( $this, 'jetpack_user_table_styles' ) );
58 add_filter( 'users_list_table_query_args', array( $this, 'set_user_query' ), 100, 1 );
59 add_action( 'admin_print_styles-user-new.php', array( $this, 'jetpack_new_users_styles' ) );
60
61 self::$tracking = new Tracking();
62 }
63
64 /**
65 * Enqueue assets for user-new.php.
66 */
67 public function jetpack_new_users_styles() {
68 Assets::register_script(
69 'jetpack-sso-admin-create-user',
70 'modules/sso/jetpack-sso-admin-create-user.js',
71 JETPACK__PLUGIN_FILE,
72 array(
73 'strategy' => 'defer',
74 'in_footer' => true,
75 'enqueue' => true,
76 )
77 );
78 }
79
80 /**
81 * Intercept the arguments for building the table, and create WP_User_Query instance
82 *
83 * @param array $args The search arguments.
84 *
85 * @return array
86 */
87 public function set_user_query( $args ) {
88 self::$user_search = new WP_User_Query( $args );
89 return $args;
90 }
91
92 /**
93 * Revokes WordPress.com invitation.
94 *
95 * @param int $user_id The user ID.
96 */
97 public function revoke_user_invite( $user_id ) {
98 try {
99 $has_pending_invite = self::has_pending_wpcom_invite( $user_id );
100
101 if ( $has_pending_invite ) {
102 $response = self::send_revoke_wpcom_invite( $has_pending_invite );
103 $event = 'sso_user_invite_revoked';
104
105 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
106 self::$tracking->record_user_event(
107 $event,
108 array(
109 'success' => 'false',
110 'error_message' => 'invalid-revoke-api-error',
111 )
112 );
113 return $response;
114 }
115
116 $body = json_decode( $response['body'] );
117
118 if ( ! $body->deleted ) {
119 self::$tracking->record_user_event(
120 $event,
121 array(
122 'success' => 'false',
123 'error_message' => 'invalid-invite-revoke',
124 )
125 );
126 } else {
127 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
128 }
129
130 return $response;
131 } else {
132 // Delete external contributor if it exists.
133 $wpcom_user_data = ( new Manager() )->get_connected_user_data( $user_id );
134 if ( isset( $wpcom_user_data['ID'] ) ) {
135 return self::delete_external_contributor( $wpcom_user_data['ID'] );
136 }
137 }
138 } catch ( Exception $e ) {
139 return false;
140 }
141 }
142
143 /**
144 * Renders invitations errors/success messages in users.php.
145 */
146 public function handle_invitation_results() {
147 $valid_nonce = isset( $_GET['_wpnonce'] )
148 ? wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'jetpack-sso-invite-user' )
149 : false;
150
151 if ( ! $valid_nonce || ! isset( $_GET['jetpack-sso-invite-user'] ) ) {
152 return;
153 }
154 if ( $_GET['jetpack-sso-invite-user'] === 'success' ) {
155 return wp_admin_notice( __( 'User was invited successfully!', 'jetpack' ), array( 'type' => 'success' ) );
156 }
157 if ( $_GET['jetpack-sso-invite-user'] === 'reinvited-success' ) {
158 return wp_admin_notice( __( 'User was re-invited successfully!', 'jetpack' ), array( 'type' => 'success' ) );
159 }
160
161 if ( $_GET['jetpack-sso-invite-user'] === 'successful-revoke' ) {
162 return wp_admin_notice( __( 'User invite revoked successfully.', 'jetpack' ), array( 'type' => 'success' ) );
163 }
164
165 if ( $_GET['jetpack-sso-invite-user'] === 'failed' && isset( $_GET['jetpack-sso-invite-error'] ) ) {
166 switch ( $_GET['jetpack-sso-invite-error'] ) {
167 case 'invalid-user':
168 return wp_admin_notice( __( 'Tried to invite a user that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
169 case 'invalid-email':
170 return wp_admin_notice( __( 'Tried to invite a user that doesn&#8217;t have an email address.', 'jetpack' ), array( 'type' => 'error' ) );
171 case 'invalid-user-permissions':
172 return wp_admin_notice( __( 'You don&#8217;t have permission to invite users.', 'jetpack' ), array( 'type' => 'error' ) );
173 case 'invalid-user-revoke':
174 return wp_admin_notice( __( 'Tried to revoke an invite for a user that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
175 case 'invalid-invite-revoke':
176 return wp_admin_notice( __( 'Tried to revoke an invite that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
177 case 'invalid-revoke-permissions':
178 return wp_admin_notice( __( 'You don&#8217;t have permission to revoke invites.', 'jetpack' ), array( 'type' => 'error' ) );
179 case 'empty-invite':
180 return wp_admin_notice( __( 'There is no previous invite for this user', 'jetpack' ), array( 'type' => 'error' ) );
181 case 'invalid-invite':
182 return wp_admin_notice( __( 'Attempted to send a new invitation to a user using an invite that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
183 case 'error-revoke':
184 return wp_admin_notice( __( 'An error has occurred when revoking the invite for the user.', 'jetpack' ), array( 'type' => 'error' ) );
185 case 'invalid-revoke-api-error':
186 return wp_admin_notice( __( 'An error has occurred when revoking the user invite.', 'jetpack' ), array( 'type' => 'error' ) );
187 default:
188 return wp_admin_notice( __( 'An error has occurred when inviting the user to the site.', 'jetpack' ), array( 'type' => 'error' ) );
189 }
190 }
191 }
192
193 /**
194 * Invites a user to connect to WordPress.com to allow them to log in via SSO.
195 */
196 public function invite_user_to_wpcom() {
197 check_admin_referer( 'jetpack-sso-invite-user', 'invite_nonce' );
198 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
199 $event = 'sso_user_invite_sent';
200
201 if ( ! current_user_can( 'create_users' ) ) {
202 $error = 'invalid-user-permissions';
203 $query_params = array(
204 'jetpack-sso-invite-user' => 'failed',
205 'jetpack-sso-invite-error' => $error,
206 '_wpnonce' => $nonce,
207 );
208 return self::create_error_notice_and_redirect( $query_params );
209 } elseif ( isset( $_GET['user_id'] ) ) {
210 $user_id = intval( wp_unslash( $_GET['user_id'] ) );
211 $user = get_user_by( 'id', $user_id );
212 $user_email = $user->user_email;
213
214 if ( ! $user || ! $user_email ) {
215 $reason = ! $user ? 'invalid-user' : 'invalid-email';
216 $query_params = array(
217 'jetpack-sso-invite-user' => 'failed',
218 'jetpack-sso-invite-error' => $reason,
219 '_wpnonce' => $nonce,
220 );
221
222 self::$tracking->record_user_event(
223 $event,
224 array(
225 'success' => 'false',
226 'error_message' => $reason,
227 )
228 );
229 return self::create_error_notice_and_redirect( $query_params );
230 }
231
232 $blog_id = Manager::get_site_id( true );
233 $roles = new Roles();
234 $user_role = $roles->translate_user_to_role( $user );
235
236 $url = '/sites/' . $blog_id . '/invites/new';
237 $response = Client::wpcom_json_api_request_as_user(
238 $url,
239 'v2',
240 array(
241 'method' => 'POST',
242 ),
243 array(
244 'invitees' => array(
245 array(
246 'email_or_username' => $user_email,
247 'role' => $user_role,
248 ),
249 ),
250 ),
251 'wpcom'
252 );
253
254 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
255 $error = 'invalid-invite-api-error';
256 $query_params = array(
257 'jetpack-sso-invite-user' => 'failed',
258 'jetpack-sso-invite-error' => $error,
259 '_wpnonce' => $nonce,
260 );
261
262 self::$tracking->record_user_event(
263 $event,
264 array(
265 'success' => 'false',
266 'error_message' => $error,
267 )
268 );
269 return self::create_error_notice_and_redirect( $query_params );
270 }
271
272 // access the first item since we're inviting one user.
273 $body = json_decode( $response['body'] )[0];
274
275 $query_params = array(
276 'jetpack-sso-invite-user' => $body->success ? 'success' : 'failed',
277 '_wpnonce' => $nonce,
278 );
279
280 if ( ! $body->success && $body->errors ) {
281 $response_error = array_keys( (array) $body->errors );
282 $query_params['jetpack-sso-invite-error'] = $response_error[0];
283 self::$tracking->record_user_event(
284 $event,
285 array(
286 'success' => 'false',
287 'error_message' => $response_error[0],
288 )
289 );
290 } else {
291 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
292 }
293
294 return self::create_error_notice_and_redirect( $query_params );
295 } else {
296 $error = 'invalid-user';
297 $query_params = array(
298 'jetpack-sso-invite-user' => 'failed',
299 'jetpack-sso-invite-error' => $error,
300 '_wpnonce' => $nonce,
301 );
302 self::$tracking->record_user_event(
303 $event,
304 array(
305 'success' => 'false',
306 'error_message' => $error,
307 )
308 );
309 return self::create_error_notice_and_redirect( $query_params );
310 }
311 wp_die();
312 }
313
314 /**
315 * Revokes a user's invitation to connect to WordPress.com.
316 *
317 * @param string $invite_id The ID of the invite to revoke.
318 */
319 public function send_revoke_wpcom_invite( $invite_id ) {
320 $blog_id = Manager::get_site_id( true );
321
322 $url = '/sites/' . $blog_id . '/invites/delete';
323 return Client::wpcom_json_api_request_as_user(
324 $url,
325 'v2',
326 array(
327 'method' => 'POST',
328 ),
329 array(
330 'invite_ids' => array( $invite_id ),
331 ),
332 'wpcom'
333 );
334 }
335
336 /**
337 * Handles logic to revoke user invite.
338 */
339 public function handle_request_revoke_invite() {
340 check_admin_referer( 'jetpack-sso-revoke-user-invite', 'revoke_invite_nonce' );
341 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
342 $event = 'sso_user_invite_revoked';
343 if ( ! current_user_can( 'promote_users' ) ) {
344 $error = 'invalid-revoke-permissions';
345 $query_params = array(
346 'jetpack-sso-invite-user' => 'failed',
347 'jetpack-sso-invite-error' => $error,
348 '_wpnonce' => $nonce,
349 );
350
351 return self::create_error_notice_and_redirect( $query_params );
352 } elseif ( isset( $_GET['user_id'] ) ) {
353 $user_id = intval( wp_unslash( $_GET['user_id'] ) );
354 $user = get_user_by( 'id', $user_id );
355 if ( ! $user ) {
356 $error = 'invalid-user-revoke';
357 $query_params = array(
358 'jetpack-sso-invite-user' => 'failed',
359 'jetpack-sso-invite-error' => $error,
360 '_wpnonce' => $nonce,
361 );
362
363 self::$tracking->record_user_event(
364 $event,
365 array(
366 'success' => 'false',
367 'error_message' => $error,
368 )
369 );
370 return self::create_error_notice_and_redirect( $query_params );
371 }
372
373 if ( ! isset( $_GET['invite_id'] ) ) {
374 $error = 'invalid-invite-revoke';
375 $query_params = array(
376 'jetpack-sso-invite-user' => 'failed',
377 'jetpack-sso-invite-error' => $error,
378 '_wpnonce' => $nonce,
379 );
380 self::$tracking->record_user_event(
381 $event,
382 array(
383 'success' => 'false',
384 'error_message' => $error,
385 )
386 );
387 return self::create_error_notice_and_redirect( $query_params );
388 }
389
390 $invite_id = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) );
391 $response = self::send_revoke_wpcom_invite( $invite_id );
392
393 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
394 $error = 'invalid-revoke-api-error';
395 $query_params = array(
396 'jetpack-sso-invite-user' => 'failed',
397 'jetpack-sso-invite-error' => $error, // general error message
398 '_wpnonce' => $nonce,
399 );
400 self::$tracking->record_user_event(
401 $event,
402 array(
403 'success' => 'false',
404 'error_message' => $error,
405 )
406 );
407 return self::create_error_notice_and_redirect( $query_params );
408 }
409
410 $body = json_decode( $response['body'] );
411 $query_params = array(
412 'jetpack-sso-invite-user' => $body->deleted ? 'successful-revoke' : 'failed',
413 '_wpnonce' => $nonce,
414 );
415 if ( ! $body->deleted ) { // no invite was deleted, probably it does not exist
416 $error = 'invalid-invite-revoke';
417 $query_params['jetpack-sso-invite-error'] = $error;
418 self::$tracking->record_user_event(
419 $event,
420 array(
421 'success' => 'false',
422 'error_message' => $error,
423 )
424 );
425 } else {
426 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
427 }
428 return self::create_error_notice_and_redirect( $query_params );
429 } else {
430 $error = 'invalid-user-revoke';
431 $query_params = array(
432 'jetpack-sso-invite-user' => 'failed',
433 'jetpack-sso-invite-error' => $error,
434 '_wpnonce' => $nonce,
435 );
436 self::$tracking->record_user_event(
437 $event,
438 array(
439 'success' => 'false',
440 'error_message' => $error,
441 )
442 );
443 return self::create_error_notice_and_redirect( $query_params );
444 }
445
446 wp_die();
447 }
448
449 /**
450 * Handles resend user invite.
451 */
452 public function handle_request_resend_invite() {
453 check_admin_referer( 'jetpack-sso-resend-user-invite', 'resend_invite_nonce' );
454 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
455 $event = 'sso_user_invite_resend';
456 if ( ! current_user_can( 'create_users' ) ) {
457 $query_params = array(
458 'jetpack-sso-invite-user' => 'failed',
459 'jetpack-sso-invite-error' => 'invalid-user-permissions',
460 '_wpnonce' => $nonce,
461 );
462 return self::create_error_notice_and_redirect( $query_params );
463 } elseif ( isset( $_GET['invite_id'] ) ) {
464 $invite_slug = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) );
465 $blog_id = Manager::get_site_id( true );
466 $url = '/sites/' . $blog_id . '/invites/resend';
467 $response = Client::wpcom_json_api_request_as_user(
468 $url,
469 'v2',
470 array(
471 'method' => 'POST',
472 ),
473 array(
474 'invite_slug' => $invite_slug,
475 ),
476 'wpcom'
477 );
478
479 $status_code = wp_remote_retrieve_response_code( $response );
480
481 if ( 200 !== $status_code ) {
482 $message_type = $status_code === 404 ? 'invalid-invite' : ''; // empty is the general error message
483 $query_params = array(
484 'jetpack-sso-invite-user' => 'failed',
485 'jetpack-sso-invite-error' => $message_type,
486 '_wpnonce' => $nonce,
487 );
488 self::$tracking->record_user_event(
489 $event,
490 array(
491 'success' => 'false',
492 'error_message' => $message_type,
493 )
494 );
495 return self::create_error_notice_and_redirect( $query_params );
496 }
497
498 $body = json_decode( $response['body'] );
499 $invite_response_message = $body->success ? 'reinvited-success' : 'failed';
500 $query_params = array(
501 'jetpack-sso-invite-user' => $invite_response_message,
502 '_wpnonce' => $nonce,
503 );
504
505 if ( ! $body->success ) {
506 self::$tracking->record_user_event(
507 $event,
508 array(
509 'success' => 'false',
510 'error_message' => $invite_response_message,
511 )
512 );
513 } else {
514 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
515 }
516
517 return self::create_error_notice_and_redirect( $query_params );
518 } else {
519 $error = 'empty-invite';
520 $query_params = array(
521 'jetpack-sso-invite-user' => 'failed',
522 'jetpack-sso-invite-error' => 'empty-invite',
523 '_wpnonce' => $nonce,
524 );
525 self::$tracking->record_user_event(
526 $event,
527 array(
528 'success' => 'false',
529 'error_message' => $error,
530 )
531 );
532 return self::create_error_notice_and_redirect( $query_params );
533 }
534 }
535
536 /**
537 * Adds 'Revoke invite' and 'Resend invite' link to user table row actions.
538 * Removes 'Reset password' link.
539 *
540 * @param array $actions - User row actions.
541 * @param WP_User $user_object - User object.
542 */
543 public function jetpack_user_table_row_actions( $actions, $user_object ) {
544 $user_id = $user_object->ID;
545 $has_pending_invite = self::has_pending_wpcom_invite( $user_id );
546
547 if ( current_user_can( 'promote_users' ) && $has_pending_invite ) {
548 $nonce = wp_create_nonce( 'jetpack-sso-revoke-user-invite' );
549 $actions['sso_revoke_invite'] = sprintf(
550 '<a class="jetpack-sso-revoke-invite-action" href="%s">%s</a>',
551 add_query_arg(
552 array(
553 'action' => 'jetpack_revoke_invite_user_to_wpcom',
554 'user_id' => $user_id,
555 'revoke_invite_nonce' => $nonce,
556 'invite_id' => $has_pending_invite,
557 ),
558 admin_url( 'admin-post.php' )
559 ),
560 esc_html__( 'Revoke invite', 'jetpack' )
561 );
562 }
563 if ( current_user_can( 'promote_users' ) && $has_pending_invite ) {
564 $nonce = wp_create_nonce( 'jetpack-sso-resend-user-invite' );
565 $actions['sso_resend_invite'] = sprintf(
566 '<a class="jetpack-sso-resend-invite-action" href="%s">%s</a>',
567 add_query_arg(
568 array(
569 'action' => 'jetpack_resend_invite_user_to_wpcom',
570 'user_id' => $user_id,
571 'resend_invite_nonce' => $nonce,
572 'invite_id' => $has_pending_invite,
573 ),
574 admin_url( 'admin-post.php' )
575 ),
576 esc_html__( 'Resend invite', 'jetpack' )
577 );
578 }
579
580 if (
581 current_user_can( 'promote_users' )
582 && (
583 $has_pending_invite
584 || Jetpack_SSO_Helpers::is_user_connected( $user_id )
585 )
586 ) {
587 unset( $actions['resetpassword'] );
588 }
589
590 return $actions;
591 }
592
593 /**
594 * Render the invitation email message.
595 */
596 public function render_invitation_email_message() {
597 $message = wp_kses(
598 __(
599 'We highly recommend inviting users to join WordPress.com and 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> to ensure maximum security and efficiency.',
600 'jetpack'
601 ),
602 array(
603 'a' => array(
604 'class' => array(),
605 'href' => array(),
606 'rel' => array(),
607 'target' => array(),
608 ),
609 )
610 );
611 wp_admin_notice(
612 $message,
613 array(
614 'id' => 'invitation_message',
615 'type' => 'info',
616 'dismissible' => false,
617 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ),
618 )
619 );
620 }
621
622 /**
623 * Render a note that wp.com invites will be automatically revoked.
624 */
625 public function render_invitations_notices_for_deleted_users() {
626 check_admin_referer( 'bulk-users' );
627
628 // When one user is deleted, the param is `user`, when multiple users are deleted, the param is `users`.
629 // We start with `users` and fallback to `user`.
630 $user_id = isset( $_GET['user'] ) ? intval( wp_unslash( $_GET['user'] ) ) : null;
631 $user_ids = isset( $_GET['users'] ) ? array_map( 'intval', wp_unslash( $_GET['users'] ) ) : array( $user_id );
632
633 $users_with_invites = array_filter(
634 $user_ids,
635 function ( $user_id ) {
636 return $user_id !== null && self::has_pending_wpcom_invite( $user_id );
637 }
638 );
639
640 $users_with_invites = array_map(
641 function ( $user_id ) {
642 $user = get_user_by( 'id', $user_id );
643 return $user->user_login;
644 },
645 $users_with_invites
646 );
647
648 $invites_count = count( $users_with_invites );
649 if ( $invites_count > 0 ) {
650 $users_with_invites = implode( ', ', $users_with_invites );
651 $message = wp_kses(
652 sprintf(
653 /* translators: %s is a comma-separated list of user logins. */
654 _n(
655 'WordPress.com invitation will be automatically revoked for user: <strong>%s</strong>.',
656 'WordPress.com invitations will be automatically revoked for users: <strong>%s</strong>.',
657 $invites_count,
658 'jetpack'
659 ),
660 $users_with_invites
661 ),
662 array( 'strong' => true )
663 );
664 wp_admin_notice(
665 $message,
666 array(
667 'id' => 'invitation_message',
668 'type' => 'info',
669 'dismissible' => false,
670 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ),
671 )
672 );
673 }
674 }
675
676 /**
677 * Render WordPress.com invite checkbox for new user registration.
678 *
679 * @param string $type The type of new user form the hook follows.
680 */
681 public function render_wpcom_invite_checkbox( $type ) {
682 /*
683 * Only check this box by default on WordPress.com sites
684 * that do not use the WooCommerce plugin.
685 */
686 $is_checked = ( new Host() )->is_wpcom_platform() && ! class_exists( 'WooCommerce' );
687
688 if ( $type === 'add-new-user' ) {
689 ?>
690 <table class="form-table">
691 <tr class="form-field">
692 <th scope="row">
693 <label for="invite_user_wpcom"><?php esc_html_e( 'Invite user', 'jetpack' ); ?></label>
694 </th>
695 <td>
696 <fieldset>
697 <legend class="screen-reader-text">
698 <span><?php esc_html_e( 'Invite user', 'jetpack' ); ?></span>
699 </legend>
700 <label for="invite_user_wpcom">
701 <input
702 name="invite_user_wpcom"
703 type="checkbox"
704 id="invite_user_wpcom"
705 <?php checked( $is_checked ); ?>
706 >
707 <?php esc_html_e( 'Invite user to WordPress.com', 'jetpack' ); ?>
708 </label>
709 </fieldset>
710 </td>
711 </tr>
712 </table>
713 <?php
714 }
715 }
716
717 /**
718 * Render a checkbox to differentiate if a user is external.
719 *
720 * @param string $type The type of new user form the hook follows.
721 */
722 public function render_wpcom_external_user_checkbox( $type ) {
723 // Only enable this feature on WordPress.com sites.
724 if ( ! ( new Host() )->is_wpcom_platform() ) {
725 return;
726 }
727
728 if ( $type === 'add-new-user' ) {
729 ?>
730 <table class="form-table">
731 <tr class="form-field">
732 <th scope="row">
733 <label for="user_external_contractor"><?php esc_html_e( 'External User', 'jetpack' ); ?></label>
734 </th>
735 <td>
736 <fieldset>
737 <legend class="screen-reader-text">
738 <span><?php esc_html_e( 'Invite user', 'jetpack' ); ?></span>
739 </legend>
740 <label for="user_external_contractor">
741 <input
742 name="user_external_contractor"
743 type="checkbox"
744 id="user_external_contractor"
745 >
746 <?php esc_html_e( 'This user is a contractor, freelancer, consultant, or agency.', 'jetpack' ); ?>
747 </label>
748 </fieldset>
749 </td>
750 </tr>
751 </table>
752 <?php
753 }
754 }
755
756 /**
757 * Render the custom email message form field for new user registration.
758 *
759 * @param string $type The type of new user form the hook follows.
760 */
761 public function render_custom_email_message_form_field( $type ) {
762 if ( $type === 'add-new-user' ) {
763 $valid_nonce = isset( $_POST['_wpnonce_create-user'] )
764 ? wp_verify_nonce( sanitize_key( $_POST['_wpnonce_create-user'] ), 'create-user' )
765 : false;
766 $custom_email_message = ( $valid_nonce && isset( $_POST['custom_email_message'] ) ) ? sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) : '';
767 ?>
768 <table class="form-table" id="custom_email_message_block">
769 <tr class="form-field">
770 <th scope="row">
771 <label for="custom_email_message"><?php esc_html_e( 'Custom Message', 'jetpack' ); ?></label>
772 </th>
773 <td>
774 <label for="custom_email_message">
775 <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>
776 <p id="custom_email_message_description">
777 <?php
778 esc_html_e( 'This user will be invited to WordPress.com. You can include a personalized welcome message with the invitation.', 'jetpack' );
779 ?>
780 </label>
781 </td>
782 </tr>
783 </table>
784 <?php
785 }
786 }
787
788 /**
789 * Conditionally disable the core invitation email.
790 * It should be sent when SSO is disabled or when admins opt-out of WordPress.com invites intentionally.
791 * If the "Send User Notification" checkbox is checked, the core invitation email should be sent.
792 *
793 * @param boolean $send_wp_email Whether the core invitation email should be sent.
794 *
795 * @return boolean Indicating if the core invitation main should be sent.
796 */
797 public function should_send_wp_mail_new_user( $send_wp_email ) {
798 if ( ! isset( $_POST['invite_user_wpcom'] ) && isset( $_POST['send_user_notification'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
799 return $send_wp_email;
800 }
801 return false;
802 }
803
804 /**
805 * Send user invitation to WordPress.com if user has no errors.
806 *
807 * @param WP_Error $errors The WP_Error object.
808 * @param bool $update Whether the user is being updated or not.
809 * @param stdClass $user The User object about to be created.
810 * @return WP_Error The modified or not WP_Error object.
811 */
812 public function send_wpcom_mail_user_invite( $errors, $update, $user ) {
813 // Only admins should be able to invite new users.
814 if ( ! current_user_can( 'create_users' ) ) {
815 return $errors;
816 }
817
818 if ( $update ) {
819 return $errors;
820 }
821
822 // check for a valid nonce.
823 if (
824 ! isset( $_POST['_wpnonce_create-user'] )
825 || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce_create-user'] ), 'create-user' )
826 ) {
827 return $errors;
828 }
829
830 // Check if the user is being invited to WordPress.com.
831 if ( ! isset( $_POST['invite_user_wpcom'] ) ) {
832 return $errors;
833 }
834
835 // check if the custom email message is too long.
836 if (
837 ! empty( $_POST['custom_email_message'] )
838 && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) ) > 500
839 ) {
840 $errors->add(
841 'custom_email_message',
842 wp_kses(
843 __( '<strong>Error</strong>: The custom message is too long. Please keep it under 500 characters.', 'jetpack' ),
844 array(
845 'strong' => array(),
846 )
847 )
848 );
849 }
850
851 $site_id = Manager::get_site_id( true );
852 if ( ! $site_id ) {
853 $errors->add(
854 'invalid_site_id',
855 wp_kses(
856 __( '<strong>Error</strong>: Invalid site ID.', 'jetpack' ),
857 array(
858 'strong' => array(),
859 )
860 )
861 );
862 }
863
864 // Bail if there are any errors.
865 if ( $errors->has_errors() ) {
866 return $errors;
867 }
868
869 $new_user_request = array(
870 'email_or_username' => sanitize_email( $user->user_email ),
871 'role' => sanitize_key( $user->role ),
872 );
873
874 if (
875 isset( $_POST['custom_email_message'] )
876 && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) > 0 )
877 ) {
878 $new_user_request['message'] = sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) );
879 }
880
881 if ( isset( $_POST['user_external_contractor'] ) ) {
882 $new_user_request['is_external'] = true;
883 }
884
885 $response = Client::wpcom_json_api_request_as_user(
886 sprintf(
887 '/sites/%d/invites/new',
888 (int) $site_id
889 ),
890 '2', // Api version
891 array(
892 'method' => 'POST',
893 ),
894 array(
895 'invitees' => array( $new_user_request ),
896 )
897 );
898
899 $event_name = 'sso_new_user_invite_sent';
900 $custom_message_sent = isset( $new_user_request['message'] ) ? 'true' : 'false';
901
902 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
903 $errors->add(
904 'invitation_not_sent',
905 wp_kses(
906 __( '<strong>Error</strong>: The user invitation email could not be sent, the user account was not created.', 'jetpack' ),
907 array(
908 'strong' => array(),
909 )
910 )
911 );
912 self::$tracking->record_user_event(
913 $event_name,
914 array(
915 'success' => 'false',
916 'error' => wp_remote_retrieve_body( $response ), // Get as much information as possible.
917 )
918 );
919 } else {
920 self::$tracking->record_user_event(
921 $event_name,
922 array(
923 'success' => 'true',
924 'custom_message_sent' => $custom_message_sent,
925 )
926 );
927 }
928
929 return $errors;
930 }
931
932 /**
933 * Adds a column in the user admin table to display user connection status and actions.
934 *
935 * @param array $columns User list table columns.
936 *
937 * @return array
938 */
939 public function jetpack_user_connected_th( $columns ) {
940 wp_enqueue_script(
941 'jetpack-sso-users',
942 plugins_url( 'modules/sso/jetpack-sso-users.js', JETPACK__PLUGIN_FILE ),
943 array(),
944 JETPACK__VERSION,
945 false
946 );
947
948 $columns['user_jetpack'] = sprintf(
949 '<span class="jetpack-sso-invitation-tooltip-icon" role="tooltip" aria-label="%3$s: %1$s" tabindex="0">%2$s [?]<span class="jetpack-sso-invitation-tooltip jetpack-sso-th-tooltip">%1$s</span></span>',
950 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' ),
951 esc_html__( 'SSO Status', 'jetpack' ),
952 esc_attr__( 'Tooltip', 'jetpack' )
953 );
954 return $columns;
955 }
956
957 /**
958 * Executed when our WP_User_Query instance is set, and we don't have cached invites.
959 * This function uses the user emails and the 'are-users-invited' endpoint to build the cache.
960 *
961 * @return void
962 */
963 private static function rebuild_invite_cache() {
964 $blog_id = Manager::get_site_id( true );
965
966 if ( self::$cached_invites === null && self::$user_search !== null ) {
967
968 self::$cached_invites = array();
969
970 $results = self::$user_search->get_results();
971
972 $user_emails = array_reduce(
973 $results,
974 function ( $current, $item ) {
975 if ( ! Jetpack_SSO_Helpers::is_user_connected( $item->ID ) ) {
976 $current[] = rawurlencode( $item->user_email );
977 } else {
978 self::$cached_invites[] = array(
979 'email_or_username' => $item->user_email,
980 'invited' => false,
981 'invite_code' => '',
982 );
983 }
984 return $current;
985 },
986 array()
987 );
988
989 if ( ! empty( $user_emails ) ) {
990 $url = '/sites/' . $blog_id . '/invites/are-users-invited';
991
992 $response = Client::wpcom_json_api_request_as_user(
993 $url,
994 'v2',
995 array(
996 'method' => 'POST',
997 ),
998 array( 'users' => $user_emails ),
999 'wpcom'
1000 );
1001
1002 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
1003 $body = json_decode( $response['body'], true );
1004
1005 // ensure array_merge happens with the right parameters
1006 if ( empty( $body ) ) {
1007 $body = array();
1008 }
1009
1010 self::$cached_invites = array_merge( self::$cached_invites, $body );
1011 }
1012 }
1013 }
1014 }
1015
1016 /**
1017 * Check if there is cached invite for a user email.
1018 *
1019 * @access private
1020 * @static
1021 *
1022 * @param string $email The user email.
1023 *
1024 * @return array|void Returns the cached invite if found.
1025 */
1026 public static function get_pending_cached_wpcom_invite( $email ) {
1027 if ( self::$cached_invites === null ) {
1028 self::rebuild_invite_cache();
1029 }
1030
1031 if ( ! empty( self::$cached_invites ) && is_array( self::$cached_invites ) ) {
1032 $index = array_search( $email, array_column( self::$cached_invites, 'email_or_username' ), true );
1033 if ( $index !== false ) {
1034 return self::$cached_invites[ $index ];
1035 }
1036 }
1037 }
1038
1039 /**
1040 * Check if a given user is invited to the site.
1041 *
1042 * @access private
1043 * @static
1044 * @param int $user_id The user ID.
1045 *
1046 * @return string|false returns the user invite code if the user is invited, false otherwise.
1047 */
1048 private static function has_pending_wpcom_invite( $user_id ) {
1049 $blog_id = Manager::get_site_id( true );
1050 $user = get_user_by( 'id', $user_id );
1051 $cached_invite = self::get_pending_cached_wpcom_invite( $user->user_email );
1052
1053 if ( $cached_invite ) {
1054 return $cached_invite['invite_code'];
1055 }
1056
1057 $url = '/sites/' . $blog_id . '/invites/is-invited';
1058 $url = add_query_arg(
1059 array(
1060 'email_or_username' => rawurlencode( $user->user_email ),
1061 ),
1062 $url
1063 );
1064 $response = Client::wpcom_json_api_request_as_user(
1065 $url,
1066 'v2',
1067 array(),
1068 null,
1069 'wpcom'
1070 );
1071
1072 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1073 return false;
1074 }
1075
1076 return json_decode( $response['body'], true )['invite_code'];
1077 }
1078
1079 /**
1080 * Delete an external contributor from the site.
1081 *
1082 * @access private
1083 * @static
1084 * @param int $user_id The user ID.
1085 *
1086 * @return bool Returns true if the user was successfully deleted, false otherwise.
1087 */
1088 private static function delete_external_contributor( $user_id ) {
1089 $blog_id = Manager::get_site_id( true );
1090 $url = '/sites/' . $blog_id . '/external-contributors/remove';
1091 $response = Client::wpcom_json_api_request_as_user(
1092 $url,
1093 'v2',
1094 array(
1095 'method' => 'POST',
1096 ),
1097 array(
1098 'user_id' => $user_id,
1099 ),
1100 'wpcom'
1101 );
1102
1103 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1104 return false;
1105 }
1106
1107 return true;
1108 }
1109
1110 /**
1111 * Show Jetpack SSO user connection status.
1112 *
1113 * @param string $val HTML for the column.
1114 * @param string $col User list table column.
1115 * @param int $user_id User ID.
1116 *
1117 * @return string
1118 */
1119 public function jetpack_show_connection_status( $val, $col, $user_id ) {
1120 if ( 'user_jetpack' === $col ) {
1121 if ( Jetpack_SSO_Helpers::is_user_connected( $user_id ) ) {
1122 $connection_html = sprintf(
1123 '<span title="%1$s" class="jetpack-sso-invitation">%2$s</span>',
1124 esc_attr__( 'This user is connected and can log-in to this site.', 'jetpack' ),
1125 esc_html__( 'Connected', 'jetpack' )
1126 );
1127 return $connection_html;
1128 } else {
1129 $has_pending_invite = self::has_pending_wpcom_invite( $user_id );
1130 if ( $has_pending_invite ) {
1131 $connection_html = sprintf(
1132 '<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
1133 esc_attr__( 'This user didn&#8217;t accept the invitation to join this site yet.', 'jetpack' ),
1134 esc_html__( 'Pending invite', 'jetpack' )
1135 );
1136 return $connection_html;
1137 }
1138 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
1139 $connection_html = sprintf(
1140 // Using formmethod and formaction because we can't nest forms and have to submit using the main form.
1141 '<a href="%1$s" class="jetpack-sso-invitation sso-disconnected-user">%2$s</a><span tabindex="0" role="tooltip" aria-label="%4$s: %3$s" class="sso-disconnected-user-icon dashicons dashicons-warning jetpack-sso-invitation-tooltip-icon">
1142 <span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip" tabindex="0">%3$s</span>
1143 </span>',
1144 add_query_arg(
1145 array(
1146 'user_id' => $user_id,
1147 'invite_nonce' => $nonce,
1148 'action' => 'jetpack_invite_user_to_wpcom',
1149 ),
1150 admin_url( 'admin-post.php' )
1151 ),
1152 esc_html__( 'Send invite', 'jetpack' ),
1153 esc_attr__( 'This user doesn&#8217;t have an SSO connection to WordPress.com. Invite them to the site to increase security and improve their experience.', 'jetpack' ),
1154 esc_attr__( 'Tooltip', 'jetpack' )
1155 );
1156 return $connection_html;
1157 }
1158 }
1159 }
1160
1161 /**
1162 * Creates error notices and redirects the user to the previous page.
1163 *
1164 * @param array $query_params - query parameters added to redirection URL.
1165 */
1166 public function create_error_notice_and_redirect( $query_params ) {
1167 $ref = wp_get_referer();
1168 if ( empty( $ref ) ) {
1169 $ref = network_admin_url( 'users.php' );
1170 }
1171
1172 $url = add_query_arg(
1173 $query_params,
1174 $ref
1175 );
1176 return wp_safe_redirect( $url );
1177 }
1178
1179 /**
1180 * Style the Jetpack user rows and columns.
1181 */
1182 public function jetpack_user_table_styles() {
1183 ?>
1184 <style>
1185 #the-list tr:has(.sso-disconnected-user) {
1186 background: #F5F1E1;
1187 }
1188 #the-list tr:has(.sso-pending-invite) {
1189 background: #E9F0F5;
1190 }
1191 .fixed .column-user_jetpack {
1192 width: 100px;
1193 }
1194 .jetpack-sso-invitation {
1195 background: none;
1196 border: none;
1197 color: #50575e;
1198 padding: 0;
1199 text-align: unset;
1200 }
1201 .jetpack-sso-invitation.sso-disconnected-user {
1202 color: #0073aa;
1203 cursor: pointer;
1204 text-decoration: underline;
1205 }
1206 .jetpack-sso-invitation.sso-disconnected-user:hover,
1207 .jetpack-sso-invitation.sso-disconnected-user:focus,
1208 .jetpack-sso-invitation.sso-disconnected-user:active {
1209 color: #0096dd;
1210 }
1211
1212 .sso-disconnected-user-icon {
1213 margin-left: 4px;
1214 cursor: pointer;
1215 background: gray;
1216 border-radius: 10px;
1217 }
1218
1219 .sso-disconnected-user-icon.dashicons {
1220 font-size: 1rem;
1221 height: 1rem;
1222 width: 1rem;
1223 background-color: #9D6E00;
1224 color: #F5F1E1;
1225 }
1226 .jetpack-sso-invitation-tooltip-icon{
1227 position: relative;
1228 cursor: pointer;
1229 }
1230 .jetpack-sso-th-tooltip {
1231 left: -170px;
1232 }
1233 .jetpack-sso-td-tooltip {
1234 left: -256px;
1235 }
1236 .jetpack-sso-invitation-tooltip {
1237 position: absolute;
1238 background: #f6f7f7;
1239 top: -85px;
1240 width: 250px;
1241 padding: 7px;
1242 color: #3c434a;
1243 font-size: .75rem;
1244 line-height: 17px;
1245 text-align: left;
1246 margin: 0;
1247 display: none;
1248 border-radius: 4px;
1249 font-family: sans-serif;
1250 box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.1);
1251 }
1252
1253 </style>
1254 <?php
1255 }
1256 }
1257 endif;
1258