PluginProbe
Members – Membership & User Role Editor Plugin / 3.2.24
Members – Membership & User Role Editor Plugin v3.2.24
3.2.25 3.2.26 3.2.24 3.2.23 3.2.22 3.2.21 trunk 0.1 0.1.1 0.2 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 2.0.0 2.0.1 2.0.2 All 66 releases
members / admin / class-roles.php

class-roles.php in Members – Membership & User Role Editor Plugin 3.2.24, at admin/class-roles.php

644 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Roles admin screen.
4 *
5 * @package Members
6 * @subpackage Admin
7 * @author The MemberPress Team
8 * @copyright Copyright (c) 2009 - 2018, The MemberPress Team
9 * @link https://members-plugin.com/
10 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
11 */
12 namespace Members\Admin;
13
14 defined('ABSPATH') || exit;
15 /**
16 * Class that displays the roles admin screen and handles requests for that page.
17 *
18 * @since 2.0.0
19 * @access public
20 */
21 final class Roles {
22
23 /**
24 * Sets up some necessary actions/filters.
25 *
26 * @since 2.0.0
27 * @access public
28 * @return void
29 */
30 public function __construct() {
31
32 // Set up some page options for the current screen.
33 add_action( 'current_screen', array( $this, 'current_screen' ) );
34
35 // Set up the role list table columns.
36 add_filter( 'manage_members_page_roles_columns', array( $this, 'manage_roles_columns' ), 5 );
37
38 // Add help tabs.
39 add_action( 'members_load_manage_roles', array( $this, 'add_help_tabs' ) );
40 }
41
42 /**
43 * Modifies the current screen object.
44 *
45 * @since 2.0.0
46 * @access public
47 * @return void
48 */
49 public function current_screen( $screen ) {
50
51 if ( 'members_page_roles' === $screen->id )
52 $screen->add_option( 'per_page', array( 'default' => 20 ) );
53 }
54
55 /**
56 * Sets up the roles column headers.
57 *
58 * @since 2.0.0
59 * @access public
60 * @param array $columns
61 * @return array
62 */
63 public function manage_roles_columns( $columns ) {
64
65 $columns = array(
66 'cb' => '<input type="checkbox" />',
67 'title' => esc_html__( 'Role Name', 'members' ),
68 'role' => esc_html__( 'Role', 'members' ),
69 'users' => esc_html__( 'Users', 'members' ),
70 'granted_caps' => esc_html__( 'Granted', 'members' ),
71 'denied_caps' => esc_html__( 'Denied', 'members' )
72 );
73
74 return apply_filters( 'members_manage_roles_columns', $columns );
75 }
76
77 /**
78 * Runs on the `load-{$page}` hook. This is the handler for form submissions and requests.
79 *
80 * @since 2.0.0
81 * @access public
82 * @return void
83 */
84 public function load() {
85
86 // Get the current action if sent as request.
87 $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false;
88
89 // Get the current action if posted.
90 if ( ( isset( $_POST['action'] ) && 'delete' == $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'delete' == $_POST['action2'] ) )
91 $action = 'bulk-delete';
92
93 if ( ( isset( $_POST['action'] ) && 'export' == $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'export' == $_POST['action2'] ) )
94 $action = 'bulk-export';
95
96 // Bulk export role handler.
97 if ( 'bulk-export' === $action ) {
98
99 if ( current_user_can( 'list_roles' ) ) {
100
101 // Verify the nonce. Nonce created via `WP_List_Table::display_tablenav()`.
102 check_admin_referer( 'bulk-roles' );
103
104 $selected_roles = array();
105
106 if ( isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ) {
107
108 foreach ( $_POST['roles'] as $role ) {
109 $selected_roles[] = members_sanitize_role( $role );
110 }
111
112 $selected_roles = array_filter( $selected_roles );
113 }
114
115 if ( ! empty( $selected_roles ) ) {
116 Role_Export::get_instance()->handle_selective_export( $selected_roles );
117 } else {
118 add_settings_error( 'members_roles', 'no_roles_selected', esc_html__( 'Please select at least one role to export.', 'members' ) );
119 }
120 }
121
122 // Bulk delete role handler.
123 } else if ( 'bulk-delete' === $action ) {
124
125 // If roles were selected, let's delete some roles.
126 if ( current_user_can( 'delete_roles' ) && isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ) {
127
128 // Verify the nonce. Nonce created via `WP_List_Table::display_tablenav()`.
129 check_admin_referer( 'bulk-roles' );
130
131 // Loop through each of the selected roles.
132 foreach ( $_POST['roles'] as $role ) {
133
134 $role = members_sanitize_role( $role );
135
136 if ( members_role_exists( $role ) )
137 members_delete_role( $role );
138 }
139
140 // Add roles deleted message.
141 add_settings_error( 'members_roles', 'roles_deleted', esc_html__( 'Selected roles deleted.', 'members' ), 'updated' );
142 }
143
144 // Delete single role handler.
145 } else if ( 'delete' === $action ) {
146
147 // Make sure the current user can delete roles.
148 if ( current_user_can( 'delete_roles' ) ) {
149
150 // Verify the referer.
151 check_admin_referer( 'delete_role', 'members_delete_role_nonce' );
152
153 // Get the role we want to delete.
154 $role = members_sanitize_role( $_GET['role'] );
155
156 // Check that we have a role before attempting to delete it.
157 if ( members_role_exists( $role ) ) {
158
159 // Add role deleted message.
160 add_settings_error( 'members_roles', 'role_deleted', sprintf( esc_html__( '%s role deleted.', 'members' ), members_get_role( $role )->get( 'label' ) ), 'updated' );
161
162 // Delete the role.
163 members_delete_role( $role );
164 }
165 }
166 }
167
168 // Redirect away from expired import preview before headers are sent.
169 if ( isset( $_GET['members_import_preview'] ) && current_user_can( 'edit_roles' ) ) {
170
171 $transient_key = 'members_import_preview_' . get_current_user_id();
172
173 if ( ! get_transient( $transient_key ) ) {
174 set_transient( 'members_import_error_' . get_current_user_id(), __( 'Your import preview has expired. Please upload the file again.', 'members' ), 30 );
175 wp_safe_redirect( add_query_arg( 'members_import_error', 'expired', members_get_edit_roles_url() ) );
176 exit;
177 }
178 }
179
180 // Display import success message.
181 if ( isset( $_GET['members_imported'] ) ) {
182
183 $message_key = 'members_import_message_' . get_current_user_id();
184 $message = get_transient( $message_key );
185
186 if ( $message ) {
187 add_settings_error( 'members_roles', 'roles_imported', esc_html( $message ), 'updated' );
188 delete_transient( $message_key );
189 }
190 }
191
192 // Display import error message.
193 if ( isset( $_GET['members_import_error'] ) ) {
194
195 $error_key = 'members_import_error_' . get_current_user_id();
196 $error = get_transient( $error_key );
197
198 if ( $error ) {
199 add_settings_error( 'members_roles', 'import_error', esc_html( $error ) );
200 delete_transient( $error_key );
201 }
202 }
203
204 // Load page hook.
205 do_action( 'members_load_manage_roles' );
206 }
207
208 /**
209 * Enqueue scripts/styles.
210 *
211 * @since 2.0.0
212 * @access public
213 * @return void
214 */
215 public function enqueue() {
216
217 wp_enqueue_style( 'members-admin' );
218 wp_enqueue_script( 'members-edit-role' );
219
220 wp_enqueue_script( 'members-import-export' );
221 }
222
223 /**
224 * Displays the page content.
225 *
226 * @since 2.0.0
227 * @access public
228 * @return void
229 */
230 public function page() {
231
232 // Show the import preview page if requested.
233 if ( isset( $_GET['members_import_preview'] ) && current_user_can( 'edit_roles' ) ) {
234 $this->render_import_preview();
235 return;
236 }
237
238 require_once( members_plugin()->dir . 'admin/class-role-list-table.php' ); ?>
239
240 <div class="wrap">
241
242 <h1>
243 <?php esc_html_e( 'Roles', 'members' ); ?>
244
245 <?php if ( current_user_can( 'create_roles' ) ) : ?>
246 <a href="<?php echo esc_url( members_get_new_role_url() ); ?>" class="page-title-action"><?php echo esc_html_x( 'Add New', 'role', 'members' ); ?></a>
247 <?php endif; ?>
248
249 <?php if ( current_user_can( 'list_roles' ) ) : ?>
250 <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=members_export_roles' ), 'members_export_roles' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Export All', 'members' ); ?></a>
251 <?php endif; ?>
252
253 <?php if ( current_user_can( 'edit_roles' ) ) : ?>
254 <a href="#members-import-roles" class="page-title-action" id="members-import-toggle"><?php esc_html_e( 'Import', 'members' ); ?></a>
255 <?php endif; ?>
256 </h1>
257
258 <?php settings_errors( 'members_roles' ); ?>
259
260 <?php if ( current_user_can( 'edit_roles' ) ) : ?>
261 <div id="members-import-roles" style="display:none; margin-top: 12px; margin-bottom: 20px;">
262
263 <div class="card" style="max-width: 800px;">
264
265 <h2 style="margin-top: 0;"><?php esc_html_e( 'Import Roles', 'members' ); ?></h2>
266
267 <form method="post" enctype="multipart/form-data" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
268
269 <?php wp_nonce_field( 'members_import_roles', 'members_import_roles_nonce' ); ?>
270 <input type="hidden" name="action" value="members_import_roles" />
271
272 <table class="form-table">
273 <tr>
274 <th scope="row"><label for="members-import-file"><?php esc_html_e( 'Import File (.json)', 'members' ); ?></label></th>
275 <td><input type="file" name="members_import_file" id="members-import-file" accept=".json" /></td>
276 </tr>
277 <tr>
278 <th scope="row"><?php esc_html_e( 'Settings', 'members' ); ?></th>
279 <td>
280 <label><input type="checkbox" name="members_import_settings" value="1" /> <?php esc_html_e( 'Also import Members plugin settings', 'members' ); ?></label>
281 </td>
282 </tr>
283 </table>
284
285 <?php submit_button( esc_html__( 'Upload and Preview', 'members' ), 'primary', 'members_import_submit' ); ?>
286
287 </form>
288
289 </div>
290
291 </div><!-- #members-import-roles -->
292 <?php endif; ?>
293
294 <div id="poststuff">
295
296 <form id="roles" action="<?php echo esc_url( members_get_edit_roles_url() ); ?>" method="post">
297
298 <?php $table = new Role_List_Table(); ?>
299 <?php $table->prepare_items(); ?>
300 <?php $table->display(); ?>
301
302 </form><!-- #roles -->
303
304 </div><!-- #poststuff -->
305
306 </div><!-- .wrap -->
307 <?php }
308
309 /**
310 * Renders the import preview/confirmation page.
311 *
312 * @since 3.4.0
313 * @access private
314 * @return void
315 */
316 private function render_import_preview() {
317
318 // Transient is guaranteed to exist — load() validates and redirects if missing.
319 $transient_key = 'members_import_preview_' . get_current_user_id();
320 $preview_data = get_transient( $transient_key );
321 $preview_data = wp_parse_args( $preview_data, array(
322 'roles' => array(),
323 'settings' => array(),
324 'import_settings' => false,
325 'meta' => array(),
326 'duplicate_slugs' => 0,
327 ) );
328 $roles = $preview_data['roles'];
329 $import_settings = ! empty( $preview_data['import_settings'] );
330 $meta = ! empty( $preview_data['meta'] ) ? $preview_data['meta'] : array();
331 $duplicate_slugs = ! empty( $preview_data['duplicate_slugs'] ) ? (int) $preview_data['duplicate_slugs'] : 0;
332 $has_conflicts = false;
333 $conflict_count = 0;
334 $current_user_roles = wp_get_current_user()->roles;
335 $default_role = get_option( 'default_role' );
336
337 foreach ( $roles as $slug => $role ) {
338 if ( ! empty( $role['conflict'] ) ) {
339 $has_conflicts = true;
340 $conflict_count++;
341 }
342
343 $roles[ $slug ]['protected'] = ( 'administrator' === $slug || in_array( $slug, $current_user_roles, true ) || $slug === $default_role || ! members_is_role_editable( $slug ) );
344 }
345
346 $new_count = count( $roles ) - $conflict_count;
347
348 ?>
349 <div class="wrap">
350
351 <h1><?php esc_html_e( 'Import Roles — Preview', 'members' ); ?></h1>
352
353 <p>
354 <?php esc_html_e( 'Review the roles below and choose what to do with each one before importing.', 'members' ); ?>
355 <?php
356 printf(
357 esc_html__( '%1$s roles found (%2$s new, %3$s existing).', 'members' ),
358 '<strong>' . number_format_i18n( count( $roles ) ) . '</strong>',
359 number_format_i18n( $new_count ),
360 number_format_i18n( $conflict_count )
361 );
362 ?>
363 </p>
364
365 <?php if ( ! empty( $meta['site_url'] ) && ! empty( $meta['export_date'] ) ) : ?>
366 <div class="notice notice-info inline" style="margin: 12px 0;">
367 <p>
368 <?php printf( esc_html__( 'Exported from %1$s on %2$s (WordPress %3$s).', 'members' ),
369 '<strong>' . esc_html( $meta['site_url'] ) . '</strong>',
370 '<strong>' . esc_html( date_i18n( get_option( 'date_format' ), strtotime( $meta['export_date'] ) ) ) . '</strong>',
371 esc_html( ! empty( $meta['wp_version'] ) ? $meta['wp_version'] : __( 'unknown', 'members' ) )
372 ); ?>
373 </p>
374 </div>
375 <?php endif; ?>
376
377 <?php if ( $duplicate_slugs > 0 ) : ?>
378 <div class="notice notice-warning inline" style="margin: 12px 0;">
379 <p>
380 <?php printf(
381 esc_html( _n(
382 '%s role was skipped because its slug matched another role after sanitization.',
383 '%s roles were skipped because their slugs matched other roles after sanitization.',
384 $duplicate_slugs,
385 'members'
386 ) ),
387 number_format_i18n( $duplicate_slugs )
388 ); ?>
389 </p>
390 </div>
391 <?php endif; ?>
392
393 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
394
395 <?php wp_nonce_field( 'members_import_roles_confirm', 'members_import_roles_confirm_nonce' ); ?>
396 <input type="hidden" name="action" value="members_import_roles_confirm" />
397
398 <?php if ( $has_conflicts ) : ?>
399 <div style="margin-bottom: 12px;">
400 <label for="members-bulk-conflict-action"><?php esc_html_e( 'Set all conflicts to:', 'members' ); ?></label>
401 <select id="members-bulk-conflict-action">
402 <option value=""><?php esc_html_e( '— No change —', 'members' ); ?></option>
403 <option value="skip"><?php esc_html_e( 'Skip', 'members' ); ?></option>
404 <option value="overwrite"><?php esc_html_e( 'Overwrite', 'members' ); ?></option>
405 </select>
406 <button type="button" class="button" id="members-apply-bulk-conflict"><?php esc_html_e( 'Apply', 'members' ); ?></button>
407 </div>
408 <?php endif; ?>
409
410 <table class="wp-list-table widefat fixed striped">
411 <thead>
412 <tr>
413 <th scope="col"><?php esc_html_e( 'Role Name', 'members' ); ?></th>
414 <th scope="col"><?php esc_html_e( 'Slug', 'members' ); ?></th>
415 <th scope="col"><?php esc_html_e( 'Capabilities', 'members' ); ?></th>
416 <th scope="col"><?php esc_html_e( 'Status', 'members' ); ?></th>
417 <th scope="col"><?php esc_html_e( 'Action', 'members' ); ?></th>
418 </tr>
419 </thead>
420 <tbody>
421 <?php foreach ( $roles as $slug => $role ) : ?>
422 <tr>
423 <td><strong><?php echo esc_html( $role['label'] ); ?></strong></td>
424 <td><code><?php echo esc_html( $slug ); ?></code></td>
425 <td>
426 <?php $cap_count = count( $role['caps'] ); ?>
427 <button type="button" class="button-link members-toggle-caps" aria-expanded="false"><?php echo absint( $cap_count ); ?></button>
428 <?php if ( $cap_count > 0 ) : ?>
429 <div class="members-caps-detail" style="display:none; margin-top: 6px;">
430 <small><?php echo esc_html( implode( ', ', array_keys( $role['caps'] ) ) ); ?></small>
431 </div>
432 <?php endif; ?>
433 </td>
434 <td>
435 <?php if ( $role['conflict'] ) : ?>
436 <span class="members-import-status members-import-status--exists"><?php esc_html_e( 'Exists', 'members' ); ?></span>
437 <?php else : ?>
438 <span class="members-import-status members-import-status--new"><?php esc_html_e( 'New', 'members' ); ?></span>
439 <?php endif; ?>
440 </td>
441 <td>
442 <?php if ( $role['conflict'] && ! empty( $role['protected'] ) ) : ?>
443 <input type="hidden" name="members_import_action[<?php echo esc_attr( $slug ); ?>]" value="skip" />
444 <span class="description"><?php esc_html_e( 'Skip (protected role)', 'members' ); ?></span>
445 <?php elseif ( $role['conflict'] ) : ?>
446 <select name="members_import_action[<?php echo esc_attr( $slug ); ?>]" class="members-import-action-select" aria-label="<?php echo esc_attr( sprintf( __( 'Action for %s', 'members' ), $role['label'] ) ); ?>">
447 <option value="skip"><?php esc_html_e( 'Skip (keep existing)', 'members' ); ?></option>
448 <option value="overwrite"><?php esc_html_e( 'Overwrite (replace all capabilities)', 'members' ); ?></option>
449 <option value="rename"><?php esc_html_e( 'Import as new (rename)', 'members' ); ?></option>
450 </select>
451 <div class="members-rename-field" style="display:none; margin-top: 8px;">
452 <label>
453 <?php esc_html_e( 'New slug:', 'members' ); ?>
454 <?php
455 $suggested_slug = $slug . '_imported';
456 $suffix_counter = 2;
457 while ( ( members_role_exists( $suggested_slug ) || get_role( $suggested_slug ) || ( isset( $roles[ $suggested_slug ] ) && $suggested_slug !== $slug ) ) && $suffix_counter <= 100 ) {
458 $suggested_slug = $slug . '_imported_' . $suffix_counter;
459 $suffix_counter++;
460 }
461 ?>
462 <input type="text" name="members_import_rename[<?php echo esc_attr( $slug ); ?>]" value="<?php echo esc_attr( $suggested_slug ); ?>" size="30" />
463 </label>
464 <p class="description"><?php esc_html_e( 'Lowercase letters, numbers, and underscores only.', 'members' ); ?></p>
465 </div>
466 <?php else : ?>
467 <select name="members_import_action[<?php echo esc_attr( $slug ); ?>]" aria-label="<?php echo esc_attr( sprintf( __( 'Action for %s', 'members' ), $role['label'] ) ); ?>">
468 <option value="import"><?php esc_html_e( 'Import', 'members' ); ?></option>
469 <option value="skip"><?php esc_html_e( 'Skip', 'members' ); ?></option>
470 </select>
471 <?php endif; ?>
472 </td>
473 </tr>
474 <?php endforeach; ?>
475 </tbody>
476 </table>
477
478 <?php if ( $import_settings ) : ?>
479 <p class="description" style="margin-top: 12px;">
480 <?php esc_html_e( 'Plugin settings will also be imported.', 'members' ); ?>
481 </p>
482 <?php endif; ?>
483
484 <p class="submit">
485 <?php submit_button( esc_html__( 'Confirm Import', 'members' ), 'primary', 'members_import_confirm_submit', false ); ?>
486 <a href="<?php echo esc_url( members_get_edit_roles_url() ); ?>" class="button" style="margin-left: 8px;"><?php esc_html_e( 'Cancel', 'members' ); ?></a>
487 </p>
488
489 </form>
490
491 </div><!-- .wrap -->
492 <?php
493 }
494
495 /**
496 * Adds help tabs.
497 *
498 * @since 2.0.0
499 * @access public
500 * @return void
501 */
502 public function add_help_tabs() {
503
504 // Get the current screen.
505 $screen = get_current_screen();
506
507 // Add overview help tab.
508 $screen->add_help_tab(
509 array(
510 'id' => 'overview',
511 'title' => esc_html__( 'Overview', 'members' ),
512 'callback' => array( $this, 'help_tab_overview' )
513 )
514 );
515
516 // Add screen content help tab.
517 $screen->add_help_tab(
518 array(
519 'id' => 'screen-content',
520 'title' => esc_html__( 'Screen Content', 'members' ),
521 'callback' => array( $this, 'help_tab_screen_content' )
522 )
523 );
524
525 // Add available actions help tab.
526 $screen->add_help_tab(
527 array(
528 'id' => 'row-actions',
529 'title' => esc_html__( 'Available Actions', 'members' ),
530 'callback' => array( $this, 'help_tab_row_actions' )
531 )
532 );
533
534 // Add bulk actions help tab.
535 $screen->add_help_tab(
536 array(
537 'id' => 'bulk-actions',
538 'title' => esc_html__( 'Bulk Actions', 'members' ),
539 'callback' => array( $this, 'help_tab_bulk_actions' )
540 )
541 );
542
543 // Add import/export help tab.
544 $screen->add_help_tab(
545 array(
546 'id' => 'import-export',
547 'title' => esc_html__( 'Import / Export', 'members' ),
548 'callback' => array( $this, 'help_tab_import_export' )
549 )
550 );
551
552 // Set the help sidebar.
553 $screen->set_help_sidebar( members_get_help_sidebar_text() );
554 }
555
556 /**
557 * Overview help tab callback function.
558 *
559 * @since 2.0.0
560 * @access public
561 * @return void
562 */
563 public function help_tab_overview() { ?>
564
565 <p>
566 <?php esc_html_e( 'This screen provides access to all of your user roles. Roles are a method of grouping users. They are made up of capabilities (caps), which give permission to users to perform specific actions on the site.' ); ?>
567 <p>
568 <?php }
569
570 /**
571 * Screen content help tab callback function.
572 *
573 * @since 2.0.0
574 * @access public
575 * @return void
576 */
577 public function help_tab_screen_content() { ?>
578
579 <p>
580 <?php esc_html_e( 'You can customize the display of this screen&#8216;s contents in a number of ways:', 'members' ); ?>
581 </p>
582
583 <ul>
584 <li><?php esc_html_e( 'You can hide/display columns based on your needs and decide how many roles to list per screen using the Screen Options tab.', 'members' ); ?></li>
585 <li><?php esc_html_e( 'You can filter the list of roles by types using the text links in the upper left. The default view is to show all roles.', 'members' ); ?></li>
586 </ul>
587 <?php }
588
589 /**
590 * Row actions help tab callback function.
591 *
592 * @since 2.0.0
593 * @access public
594 * @return void
595 */
596 public function help_tab_row_actions() { ?>
597
598 <p>
599 <?php esc_html_e( 'Hovering over a row in the roles list will display action links that allow you to manage your role. You can perform the following actions:', 'members' ); ?>
600 </p>
601
602 <ul>
603 <li><?php _e( '<strong>Edit</strong> takes you to the editing screen for that role. You can also reach that screen by clicking on the role name.', 'members' ); ?></li>
604 <li><?php _e( '<strong>Delete</strong> removes your role from this list and permanently deletes it.', 'members' ); ?></li>
605 <li><?php _e( '<strong>Clone</strong> copies the role and takes you to the new role screen to further edit it.', 'members' ); ?></li>
606 <li><?php _e( '<strong>Users</strong> takes you to the users screen and lists the users that have that role.', 'members' ); ?></li>
607 </ul>
608 <?php }
609
610 /**
611 * Bulk actions help tab callback function.
612 *
613 * @since 2.0.0
614 * @access public
615 * @return void
616 */
617 public function help_tab_bulk_actions() { ?>
618
619 <p>
620 <?php esc_html_e( 'You can permanently delete multiple roles at once. Select the roles you want to act on using the checkboxes, then select the action you want to take from the Bulk Actions menu and click Apply.', 'members' ); ?>
621 </p>
622 <?php }
623
624 /**
625 * Import/Export help tab callback function.
626 *
627 * @since 3.4.0
628 * @access public
629 * @return void
630 */
631 public function help_tab_import_export() { ?>
632
633 <p>
634 <?php esc_html_e( 'You can transfer roles between WordPress sites using the import and export tools:', 'members' ); ?>
635 </p>
636
637 <ul>
638 <li><?php _e( '<strong>Export All</strong> downloads a JSON file containing all roles, capabilities, and plugin settings.', 'members' ); ?></li>
639 <li><?php _e( '<strong>Export (Bulk Action)</strong> lets you select specific roles to export using the checkboxes.', 'members' ); ?></li>
640 <li><?php _e( '<strong>Import</strong> uploads a previously exported JSON file. You can preview all roles and choose to import, skip, overwrite, or rename each one before confirming.', 'members' ); ?></li>
641 </ul>
642 <?php }
643 }
644