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