PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.3.3
Jetpack – WP Security, Backup, Speed, & Growth v13.3.3
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
1271 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 * @phan-suppress PhanUndeclaredFunction,UnusedSuppression -- Existence of wp_admin_notice (added in WP 6.4) is checked inline.
147 * @todo Remove suppression and function_exists check when we drop support for WP 6.3.
148 */
149 public function handle_invitation_results() {
150 $valid_nonce = isset( $_GET['_wpnonce'] )
151 ? wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'jetpack-sso-invite-user' )
152 : false;
153
154 if ( ! $valid_nonce || ! isset( $_GET['jetpack-sso-invite-user'] ) || ! function_exists( 'wp_admin_notice' ) ) {
155 return;
156 }
157 if ( $_GET['jetpack-sso-invite-user'] === 'success' ) {
158 return wp_admin_notice( __( 'User was invited successfully!', 'jetpack' ), array( 'type' => 'success' ) );
159 }
160 if ( $_GET['jetpack-sso-invite-user'] === 'reinvited-success' ) {
161 return wp_admin_notice( __( 'User was re-invited successfully!', 'jetpack' ), array( 'type' => 'success' ) );
162 }
163
164 if ( $_GET['jetpack-sso-invite-user'] === 'successful-revoke' ) {
165 return wp_admin_notice( __( 'User invite revoked successfully.', 'jetpack' ), array( 'type' => 'success' ) );
166 }
167
168 if ( $_GET['jetpack-sso-invite-user'] === 'failed' && isset( $_GET['jetpack-sso-invite-error'] ) ) {
169 switch ( $_GET['jetpack-sso-invite-error'] ) {
170 case 'invalid-user':
171 return wp_admin_notice( __( 'Tried to invite a user that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
172 case 'invalid-email':
173 return wp_admin_notice( __( 'Tried to invite a user that doesn&#8217;t have an email address.', 'jetpack' ), array( 'type' => 'error' ) );
174 case 'invalid-user-permissions':
175 return wp_admin_notice( __( 'You don&#8217;t have permission to invite users.', 'jetpack' ), array( 'type' => 'error' ) );
176 case 'invalid-user-revoke':
177 return wp_admin_notice( __( 'Tried to revoke an invite for a user that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
178 case 'invalid-invite-revoke':
179 return wp_admin_notice( __( 'Tried to revoke an invite that doesn&#8217;t exist.', 'jetpack' ), array( 'type' => 'error' ) );
180 case 'invalid-revoke-permissions':
181 return wp_admin_notice( __( 'You don&#8217;t have permission to revoke invites.', 'jetpack' ), array( 'type' => 'error' ) );
182 case 'empty-invite':
183 return wp_admin_notice( __( 'There is no previous invite for this user', 'jetpack' ), array( 'type' => 'error' ) );
184 case 'invalid-invite':
185 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' ) );
186 case 'error-revoke':
187 return wp_admin_notice( __( 'An error has occurred when revoking the invite for the user.', 'jetpack' ), array( 'type' => 'error' ) );
188 case 'invalid-revoke-api-error':
189 return wp_admin_notice( __( 'An error has occurred when revoking the user invite.', 'jetpack' ), array( 'type' => 'error' ) );
190 default:
191 return wp_admin_notice( __( 'An error has occurred when inviting the user to the site.', 'jetpack' ), array( 'type' => 'error' ) );
192 }
193 }
194 }
195
196 /**
197 * Invites a user to connect to WordPress.com to allow them to log in via SSO.
198 */
199 public function invite_user_to_wpcom() {
200 check_admin_referer( 'jetpack-sso-invite-user', 'invite_nonce' );
201 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
202 $event = 'sso_user_invite_sent';
203
204 if ( ! current_user_can( 'create_users' ) ) {
205 $error = 'invalid-user-permissions';
206 $query_params = array(
207 'jetpack-sso-invite-user' => 'failed',
208 'jetpack-sso-invite-error' => $error,
209 '_wpnonce' => $nonce,
210 );
211 return self::create_error_notice_and_redirect( $query_params );
212 } elseif ( isset( $_GET['user_id'] ) ) {
213 $user_id = intval( wp_unslash( $_GET['user_id'] ) );
214 $user = get_user_by( 'id', $user_id );
215 $user_email = $user->user_email;
216
217 if ( ! $user || ! $user_email ) {
218 $reason = ! $user ? 'invalid-user' : 'invalid-email';
219 $query_params = array(
220 'jetpack-sso-invite-user' => 'failed',
221 'jetpack-sso-invite-error' => $reason,
222 '_wpnonce' => $nonce,
223 );
224
225 self::$tracking->record_user_event(
226 $event,
227 array(
228 'success' => 'false',
229 'error_message' => $reason,
230 )
231 );
232 return self::create_error_notice_and_redirect( $query_params );
233 }
234
235 $blog_id = Manager::get_site_id( true );
236 $roles = new Roles();
237 $user_role = $roles->translate_user_to_role( $user );
238
239 $url = '/sites/' . $blog_id . '/invites/new';
240 $response = Client::wpcom_json_api_request_as_user(
241 $url,
242 'v2',
243 array(
244 'method' => 'POST',
245 ),
246 array(
247 'invitees' => array(
248 array(
249 'email_or_username' => $user_email,
250 'role' => $user_role,
251 ),
252 ),
253 ),
254 'wpcom'
255 );
256
257 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
258 $error = 'invalid-invite-api-error';
259 $query_params = array(
260 'jetpack-sso-invite-user' => 'failed',
261 'jetpack-sso-invite-error' => $error,
262 '_wpnonce' => $nonce,
263 );
264
265 self::$tracking->record_user_event(
266 $event,
267 array(
268 'success' => 'false',
269 'error_message' => $error,
270 )
271 );
272 return self::create_error_notice_and_redirect( $query_params );
273 }
274
275 // access the first item since we're inviting one user.
276 $body = json_decode( $response['body'] )[0];
277
278 $query_params = array(
279 'jetpack-sso-invite-user' => $body->success ? 'success' : 'failed',
280 '_wpnonce' => $nonce,
281 );
282
283 if ( ! $body->success && $body->errors ) {
284 $response_error = array_keys( (array) $body->errors );
285 $query_params['jetpack-sso-invite-error'] = $response_error[0];
286 self::$tracking->record_user_event(
287 $event,
288 array(
289 'success' => 'false',
290 'error_message' => $response_error[0],
291 )
292 );
293 } else {
294 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
295 }
296
297 return self::create_error_notice_and_redirect( $query_params );
298 } else {
299 $error = 'invalid-user';
300 $query_params = array(
301 'jetpack-sso-invite-user' => 'failed',
302 'jetpack-sso-invite-error' => $error,
303 '_wpnonce' => $nonce,
304 );
305 self::$tracking->record_user_event(
306 $event,
307 array(
308 'success' => 'false',
309 'error_message' => $error,
310 )
311 );
312 return self::create_error_notice_and_redirect( $query_params );
313 }
314 wp_die();
315 }
316
317 /**
318 * Revokes a user's invitation to connect to WordPress.com.
319 *
320 * @param string $invite_id The ID of the invite to revoke.
321 */
322 public function send_revoke_wpcom_invite( $invite_id ) {
323 $blog_id = Manager::get_site_id( true );
324
325 $url = '/sites/' . $blog_id . '/invites/delete';
326 return Client::wpcom_json_api_request_as_user(
327 $url,
328 'v2',
329 array(
330 'method' => 'POST',
331 ),
332 array(
333 'invite_ids' => array( $invite_id ),
334 ),
335 'wpcom'
336 );
337 }
338
339 /**
340 * Handles logic to revoke user invite.
341 */
342 public function handle_request_revoke_invite() {
343 check_admin_referer( 'jetpack-sso-revoke-user-invite', 'revoke_invite_nonce' );
344 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
345 $event = 'sso_user_invite_revoked';
346 if ( ! current_user_can( 'promote_users' ) ) {
347 $error = 'invalid-revoke-permissions';
348 $query_params = array(
349 'jetpack-sso-invite-user' => 'failed',
350 'jetpack-sso-invite-error' => $error,
351 '_wpnonce' => $nonce,
352 );
353
354 return self::create_error_notice_and_redirect( $query_params );
355 } elseif ( isset( $_GET['user_id'] ) ) {
356 $user_id = intval( wp_unslash( $_GET['user_id'] ) );
357 $user = get_user_by( 'id', $user_id );
358 if ( ! $user ) {
359 $error = 'invalid-user-revoke';
360 $query_params = array(
361 'jetpack-sso-invite-user' => 'failed',
362 'jetpack-sso-invite-error' => $error,
363 '_wpnonce' => $nonce,
364 );
365
366 self::$tracking->record_user_event(
367 $event,
368 array(
369 'success' => 'false',
370 'error_message' => $error,
371 )
372 );
373 return self::create_error_notice_and_redirect( $query_params );
374 }
375
376 if ( ! isset( $_GET['invite_id'] ) ) {
377 $error = 'invalid-invite-revoke';
378 $query_params = array(
379 'jetpack-sso-invite-user' => 'failed',
380 'jetpack-sso-invite-error' => $error,
381 '_wpnonce' => $nonce,
382 );
383 self::$tracking->record_user_event(
384 $event,
385 array(
386 'success' => 'false',
387 'error_message' => $error,
388 )
389 );
390 return self::create_error_notice_and_redirect( $query_params );
391 }
392
393 $invite_id = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) );
394 $response = self::send_revoke_wpcom_invite( $invite_id );
395
396 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
397 $error = 'invalid-revoke-api-error';
398 $query_params = array(
399 'jetpack-sso-invite-user' => 'failed',
400 'jetpack-sso-invite-error' => $error, // general error message
401 '_wpnonce' => $nonce,
402 );
403 self::$tracking->record_user_event(
404 $event,
405 array(
406 'success' => 'false',
407 'error_message' => $error,
408 )
409 );
410 return self::create_error_notice_and_redirect( $query_params );
411 }
412
413 $body = json_decode( $response['body'] );
414 $query_params = array(
415 'jetpack-sso-invite-user' => $body->deleted ? 'successful-revoke' : 'failed',
416 '_wpnonce' => $nonce,
417 );
418 if ( ! $body->deleted ) { // no invite was deleted, probably it does not exist
419 $error = 'invalid-invite-revoke';
420 $query_params['jetpack-sso-invite-error'] = $error;
421 self::$tracking->record_user_event(
422 $event,
423 array(
424 'success' => 'false',
425 'error_message' => $error,
426 )
427 );
428 } else {
429 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
430 }
431 return self::create_error_notice_and_redirect( $query_params );
432 } else {
433 $error = 'invalid-user-revoke';
434 $query_params = array(
435 'jetpack-sso-invite-user' => 'failed',
436 'jetpack-sso-invite-error' => $error,
437 '_wpnonce' => $nonce,
438 );
439 self::$tracking->record_user_event(
440 $event,
441 array(
442 'success' => 'false',
443 'error_message' => $error,
444 )
445 );
446 return self::create_error_notice_and_redirect( $query_params );
447 }
448
449 wp_die();
450 }
451
452 /**
453 * Handles resend user invite.
454 */
455 public function handle_request_resend_invite() {
456 check_admin_referer( 'jetpack-sso-resend-user-invite', 'resend_invite_nonce' );
457 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
458 $event = 'sso_user_invite_resend';
459 if ( ! current_user_can( 'create_users' ) ) {
460 $query_params = array(
461 'jetpack-sso-invite-user' => 'failed',
462 'jetpack-sso-invite-error' => 'invalid-user-permissions',
463 '_wpnonce' => $nonce,
464 );
465 return self::create_error_notice_and_redirect( $query_params );
466 } elseif ( isset( $_GET['invite_id'] ) ) {
467 $invite_slug = sanitize_text_field( wp_unslash( $_GET['invite_id'] ) );
468 $blog_id = Manager::get_site_id( true );
469 $url = '/sites/' . $blog_id . '/invites/resend';
470 $response = Client::wpcom_json_api_request_as_user(
471 $url,
472 'v2',
473 array(
474 'method' => 'POST',
475 ),
476 array(
477 'invite_slug' => $invite_slug,
478 ),
479 'wpcom'
480 );
481
482 $status_code = wp_remote_retrieve_response_code( $response );
483
484 if ( 200 !== $status_code ) {
485 $message_type = $status_code === 404 ? 'invalid-invite' : ''; // empty is the general error message
486 $query_params = array(
487 'jetpack-sso-invite-user' => 'failed',
488 'jetpack-sso-invite-error' => $message_type,
489 '_wpnonce' => $nonce,
490 );
491 self::$tracking->record_user_event(
492 $event,
493 array(
494 'success' => 'false',
495 'error_message' => $message_type,
496 )
497 );
498 return self::create_error_notice_and_redirect( $query_params );
499 }
500
501 $body = json_decode( $response['body'] );
502 $invite_response_message = $body->success ? 'reinvited-success' : 'failed';
503 $query_params = array(
504 'jetpack-sso-invite-user' => $invite_response_message,
505 '_wpnonce' => $nonce,
506 );
507
508 if ( ! $body->success ) {
509 self::$tracking->record_user_event(
510 $event,
511 array(
512 'success' => 'false',
513 'error_message' => $invite_response_message,
514 )
515 );
516 } else {
517 self::$tracking->record_user_event( $event, array( 'success' => 'true' ) );
518 }
519
520 return self::create_error_notice_and_redirect( $query_params );
521 } else {
522 $error = 'empty-invite';
523 $query_params = array(
524 'jetpack-sso-invite-user' => 'failed',
525 'jetpack-sso-invite-error' => 'empty-invite',
526 '_wpnonce' => $nonce,
527 );
528 self::$tracking->record_user_event(
529 $event,
530 array(
531 'success' => 'false',
532 'error_message' => $error,
533 )
534 );
535 return self::create_error_notice_and_redirect( $query_params );
536 }
537 }
538
539 /**
540 * Adds 'Revoke invite' and 'Resend invite' link to user table row actions.
541 * Removes 'Reset password' link.
542 *
543 * @param array $actions - User row actions.
544 * @param WP_User $user_object - User object.
545 */
546 public function jetpack_user_table_row_actions( $actions, $user_object ) {
547 $user_id = $user_object->ID;
548 $has_pending_invite = self::has_pending_wpcom_invite( $user_id );
549
550 if ( current_user_can( 'promote_users' ) && $has_pending_invite ) {
551 $nonce = wp_create_nonce( 'jetpack-sso-revoke-user-invite' );
552 $actions['sso_revoke_invite'] = sprintf(
553 '<a class="jetpack-sso-revoke-invite-action" href="%s">%s</a>',
554 add_query_arg(
555 array(
556 'action' => 'jetpack_revoke_invite_user_to_wpcom',
557 'user_id' => $user_id,
558 'revoke_invite_nonce' => $nonce,
559 'invite_id' => $has_pending_invite,
560 ),
561 admin_url( 'admin-post.php' )
562 ),
563 esc_html__( 'Revoke invite', 'jetpack' )
564 );
565 }
566 if ( current_user_can( 'promote_users' ) && $has_pending_invite ) {
567 $nonce = wp_create_nonce( 'jetpack-sso-resend-user-invite' );
568 $actions['sso_resend_invite'] = sprintf(
569 '<a class="jetpack-sso-resend-invite-action" href="%s">%s</a>',
570 add_query_arg(
571 array(
572 'action' => 'jetpack_resend_invite_user_to_wpcom',
573 'user_id' => $user_id,
574 'resend_invite_nonce' => $nonce,
575 'invite_id' => $has_pending_invite,
576 ),
577 admin_url( 'admin-post.php' )
578 ),
579 esc_html__( 'Resend invite', 'jetpack' )
580 );
581 }
582
583 if (
584 current_user_can( 'promote_users' )
585 && (
586 $has_pending_invite
587 || Jetpack_SSO_Helpers::is_user_connected( $user_id )
588 )
589 ) {
590 unset( $actions['resetpassword'] );
591 }
592
593 return $actions;
594 }
595
596 /**
597 * Render the invitation email message.
598 */
599 public function render_invitation_email_message() {
600 // @todo Remove function_exists check (and phan suppression below) when we drop support for WP 6.3.
601 if ( ! function_exists( 'wp_admin_notice' ) ) {
602 return;
603 }
604 $message = wp_kses(
605 __(
606 '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.',
607 'jetpack'
608 ),
609 array(
610 'a' => array(
611 'class' => array(),
612 'href' => array(),
613 'rel' => array(),
614 'target' => array(),
615 ),
616 )
617 );
618 // @phan-suppress-next-line PhanUndeclaredFunction -- Existence of wp_admin_notice (added in WP 6.4) is checked above. @phan-suppress-current-line UnusedPluginSuppression
619 wp_admin_notice(
620 $message,
621 array(
622 'id' => 'invitation_message',
623 'type' => 'info',
624 'dismissible' => false,
625 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ),
626 )
627 );
628 }
629
630 /**
631 * Render a note that wp.com invites will be automatically revoked.
632 */
633 public function render_invitations_notices_for_deleted_users() {
634 // @todo Remove function_exists check (and phan suppression below) when we drop support for WP 6.3.
635 if ( ! function_exists( 'wp_admin_notice' ) ) {
636 return;
637 }
638 check_admin_referer( 'bulk-users' );
639
640 // When one user is deleted, the param is `user`, when multiple users are deleted, the param is `users`.
641 // We start with `users` and fallback to `user`.
642 $user_id = isset( $_GET['user'] ) ? intval( wp_unslash( $_GET['user'] ) ) : null;
643 $user_ids = isset( $_GET['users'] ) ? array_map( 'intval', wp_unslash( $_GET['users'] ) ) : array( $user_id );
644
645 $users_with_invites = array_filter(
646 $user_ids,
647 function ( $user_id ) {
648 return $user_id !== null && self::has_pending_wpcom_invite( $user_id );
649 }
650 );
651
652 $users_with_invites = array_map(
653 function ( $user_id ) {
654 $user = get_user_by( 'id', $user_id );
655 return $user->user_login;
656 },
657 $users_with_invites
658 );
659
660 $invites_count = count( $users_with_invites );
661 if ( $invites_count > 0 ) {
662 $users_with_invites = implode( ', ', $users_with_invites );
663 $message = wp_kses(
664 sprintf(
665 /* translators: %s is a comma-separated list of user logins. */
666 _n(
667 'WordPress.com invitation will be automatically revoked for user: <strong>%s</strong>.',
668 'WordPress.com invitations will be automatically revoked for users: <strong>%s</strong>.',
669 $invites_count,
670 'jetpack'
671 ),
672 $users_with_invites
673 ),
674 array( 'strong' => true )
675 );
676 // @phan-suppress-next-line PhanUndeclaredFunction -- Existence of wp_admin_notice (added in WP 6.4) is checked above. @phan-suppress-current-line UnusedPluginSuppression
677 wp_admin_notice(
678 $message,
679 array(
680 'id' => 'invitation_message',
681 'type' => 'info',
682 'dismissible' => false,
683 'additional_classes' => array( 'jetpack-sso-admin-create-user-invite-message' ),
684 )
685 );
686 }
687 }
688
689 /**
690 * Render WordPress.com invite checkbox for new user registration.
691 *
692 * @param string $type The type of new user form the hook follows.
693 */
694 public function render_wpcom_invite_checkbox( $type ) {
695 /*
696 * Only check this box by default on WordPress.com sites
697 * that do not use the WooCommerce plugin.
698 */
699 $is_checked = ( new Host() )->is_wpcom_platform() && ! class_exists( 'WooCommerce' );
700
701 if ( $type === 'add-new-user' ) {
702 ?>
703 <table class="form-table">
704 <tr class="form-field">
705 <th scope="row">
706 <label for="invite_user_wpcom"><?php esc_html_e( 'Invite user', 'jetpack' ); ?></label>
707 </th>
708 <td>
709 <fieldset>
710 <legend class="screen-reader-text">
711 <span><?php esc_html_e( 'Invite user', 'jetpack' ); ?></span>
712 </legend>
713 <label for="invite_user_wpcom">
714 <input
715 name="invite_user_wpcom"
716 type="checkbox"
717 id="invite_user_wpcom"
718 <?php checked( $is_checked ); ?>
719 >
720 <?php esc_html_e( 'Invite user to WordPress.com', 'jetpack' ); ?>
721 </label>
722 </fieldset>
723 </td>
724 </tr>
725 </table>
726 <?php
727 }
728 }
729
730 /**
731 * Render a checkbox to differentiate if a user is external.
732 *
733 * @param string $type The type of new user form the hook follows.
734 */
735 public function render_wpcom_external_user_checkbox( $type ) {
736 // Only enable this feature on WordPress.com sites.
737 if ( ! ( new Host() )->is_wpcom_platform() ) {
738 return;
739 }
740
741 if ( $type === 'add-new-user' ) {
742 ?>
743 <table class="form-table">
744 <tr class="form-field">
745 <th scope="row">
746 <label for="user_external_contractor"><?php esc_html_e( 'External User', 'jetpack' ); ?></label>
747 </th>
748 <td>
749 <fieldset>
750 <legend class="screen-reader-text">
751 <span><?php esc_html_e( 'Invite user', 'jetpack' ); ?></span>
752 </legend>
753 <label for="user_external_contractor">
754 <input
755 name="user_external_contractor"
756 type="checkbox"
757 id="user_external_contractor"
758 >
759 <?php esc_html_e( 'This user is a contractor, freelancer, consultant, or agency.', 'jetpack' ); ?>
760 </label>
761 </fieldset>
762 </td>
763 </tr>
764 </table>
765 <?php
766 }
767 }
768
769 /**
770 * Render the custom email message form field for new user registration.
771 *
772 * @param string $type The type of new user form the hook follows.
773 */
774 public function render_custom_email_message_form_field( $type ) {
775 if ( $type === 'add-new-user' ) {
776 $valid_nonce = isset( $_POST['_wpnonce_create-user'] )
777 ? wp_verify_nonce( sanitize_key( $_POST['_wpnonce_create-user'] ), 'create-user' )
778 : false;
779 $custom_email_message = ( $valid_nonce && isset( $_POST['custom_email_message'] ) ) ? sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) : '';
780 ?>
781 <table class="form-table" id="custom_email_message_block">
782 <tr class="form-field">
783 <th scope="row">
784 <label for="custom_email_message"><?php esc_html_e( 'Custom Message', 'jetpack' ); ?></label>
785 </th>
786 <td>
787 <label for="custom_email_message">
788 <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>
789 <p id="custom_email_message_description">
790 <?php
791 esc_html_e( 'This user will be invited to WordPress.com. You can include a personalized welcome message with the invitation.', 'jetpack' );
792 ?>
793 </label>
794 </td>
795 </tr>
796 </table>
797 <?php
798 }
799 }
800
801 /**
802 * Conditionally disable the core invitation email.
803 * It should be sent when SSO is disabled or when admins opt-out of WordPress.com invites intentionally.
804 * If the "Send User Notification" checkbox is checked, the core invitation email should be sent.
805 *
806 * @param boolean $send_wp_email Whether the core invitation email should be sent.
807 *
808 * @return boolean Indicating if the core invitation main should be sent.
809 */
810 public function should_send_wp_mail_new_user( $send_wp_email ) {
811 if ( ! isset( $_POST['invite_user_wpcom'] ) && isset( $_POST['send_user_notification'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
812 return $send_wp_email;
813 }
814 return false;
815 }
816
817 /**
818 * Send user invitation to WordPress.com if user has no errors.
819 *
820 * @param WP_Error $errors The WP_Error object.
821 * @param bool $update Whether the user is being updated or not.
822 * @param stdClass $user The User object about to be created.
823 * @return WP_Error The modified or not WP_Error object.
824 */
825 public function send_wpcom_mail_user_invite( $errors, $update, $user ) {
826 // Only admins should be able to invite new users.
827 if ( ! current_user_can( 'create_users' ) ) {
828 return $errors;
829 }
830
831 if ( $update ) {
832 return $errors;
833 }
834
835 // check for a valid nonce.
836 if (
837 ! isset( $_POST['_wpnonce_create-user'] )
838 || ! wp_verify_nonce( sanitize_key( $_POST['_wpnonce_create-user'] ), 'create-user' )
839 ) {
840 return $errors;
841 }
842
843 // Check if the user is being invited to WordPress.com.
844 if ( ! isset( $_POST['invite_user_wpcom'] ) ) {
845 return $errors;
846 }
847
848 // check if the custom email message is too long.
849 if (
850 ! empty( $_POST['custom_email_message'] )
851 && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) ) > 500
852 ) {
853 $errors->add(
854 'custom_email_message',
855 wp_kses(
856 __( '<strong>Error</strong>: The custom message is too long. Please keep it under 500 characters.', 'jetpack' ),
857 array(
858 'strong' => array(),
859 )
860 )
861 );
862 }
863
864 $site_id = Manager::get_site_id( true );
865 if ( ! $site_id ) {
866 $errors->add(
867 'invalid_site_id',
868 wp_kses(
869 __( '<strong>Error</strong>: Invalid site ID.', 'jetpack' ),
870 array(
871 'strong' => array(),
872 )
873 )
874 );
875 }
876
877 // Bail if there are any errors.
878 if ( $errors->has_errors() ) {
879 return $errors;
880 }
881
882 $new_user_request = array(
883 'email_or_username' => sanitize_email( $user->user_email ),
884 'role' => sanitize_key( $user->role ),
885 );
886
887 if (
888 isset( $_POST['custom_email_message'] )
889 && strlen( sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) ) > 0 )
890 ) {
891 $new_user_request['message'] = sanitize_text_field( wp_unslash( $_POST['custom_email_message'] ) );
892 }
893
894 if ( isset( $_POST['user_external_contractor'] ) ) {
895 $new_user_request['is_external'] = true;
896 }
897
898 $response = Client::wpcom_json_api_request_as_user(
899 sprintf(
900 '/sites/%d/invites/new',
901 (int) $site_id
902 ),
903 '2', // Api version
904 array(
905 'method' => 'POST',
906 ),
907 array(
908 'invitees' => array( $new_user_request ),
909 )
910 );
911
912 $event_name = 'sso_new_user_invite_sent';
913 $custom_message_sent = isset( $new_user_request['message'] ) ? 'true' : 'false';
914
915 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
916 $errors->add(
917 'invitation_not_sent',
918 wp_kses(
919 __( '<strong>Error</strong>: The user invitation email could not be sent, the user account was not created.', 'jetpack' ),
920 array(
921 'strong' => array(),
922 )
923 )
924 );
925 self::$tracking->record_user_event(
926 $event_name,
927 array(
928 'success' => 'false',
929 'error' => wp_remote_retrieve_body( $response ), // Get as much information as possible.
930 )
931 );
932 } else {
933 self::$tracking->record_user_event(
934 $event_name,
935 array(
936 'success' => 'true',
937 'custom_message_sent' => $custom_message_sent,
938 )
939 );
940 }
941
942 return $errors;
943 }
944
945 /**
946 * Adds a column in the user admin table to display user connection status and actions.
947 *
948 * @param array $columns User list table columns.
949 *
950 * @return array
951 */
952 public function jetpack_user_connected_th( $columns ) {
953 wp_enqueue_script(
954 'jetpack-sso-users',
955 plugins_url( 'modules/sso/jetpack-sso-users.js', JETPACK__PLUGIN_FILE ),
956 array(),
957 JETPACK__VERSION,
958 false
959 );
960
961 $columns['user_jetpack'] = sprintf(
962 '<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>',
963 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' ),
964 esc_html__( 'SSO Status', 'jetpack' ),
965 esc_attr__( 'Tooltip', 'jetpack' )
966 );
967 return $columns;
968 }
969
970 /**
971 * Executed when our WP_User_Query instance is set, and we don't have cached invites.
972 * This function uses the user emails and the 'are-users-invited' endpoint to build the cache.
973 *
974 * @return void
975 */
976 private static function rebuild_invite_cache() {
977 $blog_id = Manager::get_site_id( true );
978
979 if ( self::$cached_invites === null && self::$user_search !== null ) {
980
981 self::$cached_invites = array();
982
983 $results = self::$user_search->get_results();
984
985 $user_emails = array_reduce(
986 $results,
987 function ( $current, $item ) {
988 if ( ! Jetpack_SSO_Helpers::is_user_connected( $item->ID ) ) {
989 $current[] = rawurlencode( $item->user_email );
990 } else {
991 self::$cached_invites[] = array(
992 'email_or_username' => $item->user_email,
993 'invited' => false,
994 'invite_code' => '',
995 );
996 }
997 return $current;
998 },
999 array()
1000 );
1001
1002 if ( ! empty( $user_emails ) ) {
1003 $url = '/sites/' . $blog_id . '/invites/are-users-invited';
1004
1005 $response = Client::wpcom_json_api_request_as_user(
1006 $url,
1007 'v2',
1008 array(
1009 'method' => 'POST',
1010 ),
1011 array( 'users' => $user_emails ),
1012 'wpcom'
1013 );
1014
1015 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
1016 $body = json_decode( $response['body'], true );
1017
1018 // ensure array_merge happens with the right parameters
1019 if ( empty( $body ) ) {
1020 $body = array();
1021 }
1022
1023 self::$cached_invites = array_merge( self::$cached_invites, $body );
1024 }
1025 }
1026 }
1027 }
1028
1029 /**
1030 * Check if there is cached invite for a user email.
1031 *
1032 * @access private
1033 * @static
1034 *
1035 * @param string $email The user email.
1036 *
1037 * @return array|void Returns the cached invite if found.
1038 */
1039 public static function get_pending_cached_wpcom_invite( $email ) {
1040 if ( self::$cached_invites === null ) {
1041 self::rebuild_invite_cache();
1042 }
1043
1044 if ( ! empty( self::$cached_invites ) && is_array( self::$cached_invites ) ) {
1045 $index = array_search( $email, array_column( self::$cached_invites, 'email_or_username' ), true );
1046 if ( $index !== false ) {
1047 return self::$cached_invites[ $index ];
1048 }
1049 }
1050 }
1051
1052 /**
1053 * Check if a given user is invited to the site.
1054 *
1055 * @access private
1056 * @static
1057 * @param int $user_id The user ID.
1058 *
1059 * @return {false|string} returns the user invite code if the user is invited, false otherwise.
1060 */
1061 private static function has_pending_wpcom_invite( $user_id ) {
1062 $blog_id = Manager::get_site_id( true );
1063 $user = get_user_by( 'id', $user_id );
1064 $cached_invite = self::get_pending_cached_wpcom_invite( $user->user_email );
1065
1066 if ( $cached_invite ) {
1067 return $cached_invite['invite_code'];
1068 }
1069
1070 $url = '/sites/' . $blog_id . '/invites/is-invited';
1071 $url = add_query_arg(
1072 array(
1073 'email_or_username' => rawurlencode( $user->user_email ),
1074 ),
1075 $url
1076 );
1077 $response = Client::wpcom_json_api_request_as_user(
1078 $url,
1079 'v2',
1080 array(),
1081 null,
1082 'wpcom'
1083 );
1084
1085 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1086 return false;
1087 }
1088
1089 return json_decode( $response['body'], true )['invite_code'];
1090 }
1091
1092 /**
1093 * Delete an external contributor from the site.
1094 *
1095 * @access private
1096 * @static
1097 * @param int $user_id The user ID.
1098 *
1099 * @return bool Returns true if the user was successfully deleted, false otherwise.
1100 */
1101 private static function delete_external_contributor( $user_id ) {
1102 $blog_id = Manager::get_site_id( true );
1103 $url = '/sites/' . $blog_id . '/external-contributors/remove';
1104 $response = Client::wpcom_json_api_request_as_user(
1105 $url,
1106 'v2',
1107 array(
1108 'method' => 'POST',
1109 ),
1110 array(
1111 'user_id' => $user_id,
1112 ),
1113 'wpcom'
1114 );
1115
1116 if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
1117 return false;
1118 }
1119
1120 return true;
1121 }
1122
1123 /**
1124 * Show Jetpack SSO user connection status.
1125 *
1126 * @param string $val HTML for the column.
1127 * @param string $col User list table column.
1128 * @param int $user_id User ID.
1129 *
1130 * @return string
1131 */
1132 public function jetpack_show_connection_status( $val, $col, $user_id ) {
1133 if ( 'user_jetpack' === $col ) {
1134 if ( Jetpack_SSO_Helpers::is_user_connected( $user_id ) ) {
1135 $connection_html = sprintf(
1136 '<span title="%1$s" class="jetpack-sso-invitation">%2$s</span>',
1137 esc_attr__( 'This user is connected and can log-in to this site.', 'jetpack' ),
1138 esc_html__( 'Connected', 'jetpack' )
1139 );
1140 return $connection_html;
1141 } else {
1142 $has_pending_invite = self::has_pending_wpcom_invite( $user_id );
1143 if ( $has_pending_invite ) {
1144 $connection_html = sprintf(
1145 '<span title="%1$s" class="jetpack-sso-invitation sso-pending-invite">%2$s</span>',
1146 esc_attr__( 'This user didn&#8217;t accept the invitation to join this site yet.', 'jetpack' ),
1147 esc_html__( 'Pending invite', 'jetpack' )
1148 );
1149 return $connection_html;
1150 }
1151 $nonce = wp_create_nonce( 'jetpack-sso-invite-user' );
1152 $connection_html = sprintf(
1153 // Using formmethod and formaction because we can't nest forms and have to submit using the main form.
1154 '<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">
1155 <span class="jetpack-sso-invitation-tooltip jetpack-sso-td-tooltip" tabindex="0">%3$s</span>
1156 </span>',
1157 add_query_arg(
1158 array(
1159 'user_id' => $user_id,
1160 'invite_nonce' => $nonce,
1161 'action' => 'jetpack_invite_user_to_wpcom',
1162 ),
1163 admin_url( 'admin-post.php' )
1164 ),
1165 esc_html__( 'Send invite', 'jetpack' ),
1166 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' ),
1167 esc_attr__( 'Tooltip', 'jetpack' )
1168 );
1169 return $connection_html;
1170 }
1171 }
1172 }
1173
1174 /**
1175 * Creates error notices and redirects the user to the previous page.
1176 *
1177 * @param array $query_params - query parameters added to redirection URL.
1178 */
1179 public function create_error_notice_and_redirect( $query_params ) {
1180 $ref = wp_get_referer();
1181 if ( empty( $ref ) ) {
1182 $ref = network_admin_url( 'users.php' );
1183 }
1184
1185 $url = add_query_arg(
1186 $query_params,
1187 $ref
1188 );
1189 return wp_safe_redirect( $url );
1190 }
1191
1192 /**
1193 * Style the Jetpack user rows and columns.
1194 */
1195 public function jetpack_user_table_styles() {
1196 ?>
1197 <style>
1198 #the-list tr:has(.sso-disconnected-user) {
1199 background: #F5F1E1;
1200 }
1201 #the-list tr:has(.sso-pending-invite) {
1202 background: #E9F0F5;
1203 }
1204 .fixed .column-user_jetpack {
1205 width: 100px;
1206 }
1207 .jetpack-sso-invitation {
1208 background: none;
1209 border: none;
1210 color: #50575e;
1211 padding: 0;
1212 text-align: unset;
1213 }
1214 .jetpack-sso-invitation.sso-disconnected-user {
1215 color: #0073aa;
1216 cursor: pointer;
1217 text-decoration: underline;
1218 }
1219 .jetpack-sso-invitation.sso-disconnected-user:hover,
1220 .jetpack-sso-invitation.sso-disconnected-user:focus,
1221 .jetpack-sso-invitation.sso-disconnected-user:active {
1222 color: #0096dd;
1223 }
1224
1225 .sso-disconnected-user-icon {
1226 margin-left: 4px;
1227 cursor: pointer;
1228 background: gray;
1229 border-radius: 10px;
1230 }
1231
1232 .sso-disconnected-user-icon.dashicons {
1233 font-size: 1rem;
1234 height: 1rem;
1235 width: 1rem;
1236 background-color: #9D6E00;
1237 color: #F5F1E1;
1238 }
1239 .jetpack-sso-invitation-tooltip-icon{
1240 position: relative;
1241 cursor: pointer;
1242 }
1243 .jetpack-sso-th-tooltip {
1244 left: -170px;
1245 }
1246 .jetpack-sso-td-tooltip {
1247 left: -256px;
1248 }
1249 .jetpack-sso-invitation-tooltip {
1250 position: absolute;
1251 background: #f6f7f7;
1252 top: -85px;
1253 width: 250px;
1254 padding: 7px;
1255 color: #3c434a;
1256 font-size: .75rem;
1257 line-height: 17px;
1258 text-align: left;
1259 margin: 0;
1260 display: none;
1261 border-radius: 4px;
1262 font-family: sans-serif;
1263 box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.1);
1264 }
1265
1266 </style>
1267 <?php
1268 }
1269 }
1270 endif;
1271