1<?php
2/**
3 * List Table API: WP_Users_List_Table class
4 *
5 * @package WordPress
6 * @subpackage Administration
7 * @since 3.1.0
8 */
9
10/**
11 * Core class used to implement displaying users in a list table.
12 *
13 * @since 3.1.0
14 *
15 * @see WP_List_Table
16 */
17class WP_Users_List_Table extends WP_List_Table {
18
19 /**
20 * Site ID to generate the Users list table for.
21 *
22 * @since 3.1.0
23 * @var int
24 */
25 public $site_id;
26
27 /**
28 * Whether or not the current Users list table is for Multisite.
29 *
30 * @since 3.1.0
31 * @var bool
32 */
33 public $is_site_users;
34
35 /**
36 * Constructor.
37 *
38 * @since 3.1.0
39 *
40 * @see WP_List_Table::__construct() for more information on default arguments.
41 *
42 * @param array $args An associative array of arguments.
43 */
44 public function __construct( $args = array() ) {
45 parent::__construct(
46 array(
47 'singular' => 'user',
48 'plural' => 'users',
49 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
50 )
51 );
52
53 $this->is_site_users = 'site-users-network' === $this->screen->id;
54
55 if ( $this->is_site_users ) {
56 $this->site_id = isset( $_REQUEST['id'] ) ? (int) $_REQUEST['id'] : 0;
57 }
58 }
59
60 /**
61 * Checks the current user's permissions.
62 *
63 * @since 3.1.0
64 *
65 * @return bool
66 */
67 public function ajax_user_can() {
68 if ( $this->is_site_users ) {
69 return current_user_can( 'manage_sites' );
70 } else {
71 return current_user_can( 'list_users' );
72 }
73 }
74
75 /**
76 * Prepares the users list for display.
77 *
78 * @since 3.1.0
79 *
80 * @global string $role
81 * @global string $usersearch
82 */
83 public function prepare_items() {
84 global $role, $usersearch;
85
86 $usersearch = isset( $_REQUEST['s'] ) ? wp_unslash( trim( $_REQUEST['s'] ) ) : '';
87
88 $role = isset( $_REQUEST['role'] ) ? $_REQUEST['role'] : '';
89
90 $per_page = ( $this->is_site_users ) ? 'site_users_network_per_page' : 'users_per_page';
91 $users_per_page = $this->get_items_per_page( $per_page );
92
93 $paged = $this->get_pagenum();
94
95 if ( 'none' === $role ) {
96 $args = array(
97 'number' => $users_per_page,
98 'offset' => ( $paged - 1 ) * $users_per_page,
99 'include' => wp_get_users_with_no_role( $this->site_id ),
100 'search' => $usersearch,
101 'fields' => 'all_with_meta',
102 );
103 } else {
104 $args = array(
105 'number' => $users_per_page,
106 'offset' => ( $paged - 1 ) * $users_per_page,
107 'role' => $role,
108 'search' => $usersearch,
109 'fields' => 'all_with_meta',
110 );
111 }
112
113 if ( '' !== $args['search'] ) {
114 $args['search'] = '*' . $args['search'] . '*';
115 }
116
117 if ( $this->is_site_users ) {
118 $args['blog_id'] = $this->site_id;
119 }
120
121 if ( isset( $_REQUEST['orderby'] ) ) {
122 $args['orderby'] = $_REQUEST['orderby'];
123 }
124
125 if ( isset( $_REQUEST['order'] ) ) {
126 $args['order'] = $_REQUEST['order'];
127 }
128
129 /**
130 * Filters the query arguments used to retrieve users for the current users list table.
131 *
132 * @since 4.4.0
133 *
134 * @param array $args Arguments passed to WP_User_Query to retrieve items for the current
135 * users list table.
136 */
137 $args = apply_filters( 'users_list_table_query_args', $args );
138
139 // Query the user IDs for this page.
140 $wp_user_search = new WP_User_Query( $args );
141
142 $this->items = $wp_user_search->get_results();
143
144 $this->set_pagination_args(
145 array(
146 'total_items' => $wp_user_search->get_total(),
147 'per_page' => $users_per_page,
148 )
149 );
150 }
151
152 /**
153 * Outputs 'no users' message.
154 *
155 * @since 3.1.0
156 */
157 public function no_items() {
158 _e( 'No users found.' );
159 }
160
161 /**
162 * Returns an associative array listing all the views that can be used
163 * with this table.
164 *
165 * Provides a list of roles and user count for that role for easy
166 * filtering of the user table.
167 *
168 * @since 3.1.0
169 *
170 * @global string $role
171 *
172 * @return string[] An array of HTML links keyed by their view.
173 */
174 protected function get_views() {
175 global $role;
176
177 $wp_roles = wp_roles();
178
179 $count_users = ! wp_is_large_user_count();
180
181 if ( $this->is_site_users ) {
182 $url = 'site-users.php?id=' . $this->site_id;
183 } else {
184 $url = 'users.php';
185 }
186
187 $role_links = array();
188 $avail_roles = array();
189 $all_text = __( 'All' );
190
191 if ( $count_users ) {
192 if ( $this->is_site_users ) {
193 switch_to_blog( $this->site_id );
194 $users_of_blog = count_users( 'time', $this->site_id );
195 restore_current_blog();
196 } else {
197 $users_of_blog = count_users();
198 }
199
200 $total_users = $users_of_blog['total_users'];
201 $avail_roles =& $users_of_blog['avail_roles'];
202 unset( $users_of_blog );
203
204 $all_text = sprintf(
205 /* translators: %s: Number of users. */
206 _nx(
207 'All <span class="count">(%s)</span>',
208 'All <span class="count">(%s)</span>',
209 $total_users,
210 'users'
211 ),
212 number_format_i18n( $total_users )
213 );
214 }
215
216 $role_links['all'] = array(
217 'url' => $url,
218 'label' => $all_text,
219 'current' => empty( $role ),
220 );
221
222 foreach ( $wp_roles->get_names() as $this_role => $name ) {
223 if ( $count_users && ! isset( $avail_roles[ $this_role ] ) ) {
224 continue;
225 }
226
227 $name = translate_user_role( $name );
228 if ( $count_users ) {
229 $name = sprintf(
230 /* translators: 1: User role name, 2: Number of users. */
231 __( '%1$s <span class="count">(%2$s)</span>' ),
232 $name,
233 number_format_i18n( $avail_roles[ $this_role ] )
234 );
235 }
236
237 $role_links[ $this_role ] = array(
238 'url' => esc_url( add_query_arg( 'role', $this_role, $url ) ),
239 'label' => $name,
240 'current' => $this_role === $role,
241 );
242 }
243
244 if ( ! empty( $avail_roles['none'] ) ) {
245
246 $name = __( 'No role' );
247 $name = sprintf(
248 /* translators: 1: User role name, 2: Number of users. */
249 __( '%1$s <span class="count">(%2$s)</span>' ),
250 $name,
251 number_format_i18n( $avail_roles['none'] )
252 );
253
254 $role_links['none'] = array(
255 'url' => esc_url( add_query_arg( 'role', 'none', $url ) ),
256 'label' => $name,
257 'current' => 'none' === $role,
258 );
259 }
260
261 return $this->get_views_links( $role_links );
262 }
263
264 /**
265 * Retrieves an associative array of bulk actions available on this table.
266 *
267 * @since 3.1.0
268 *
269 * @return array Array of bulk action labels keyed by their action.
270 */
271 protected function get_bulk_actions() {
272 $actions = array();
273
274 if ( is_multisite() ) {
275 if ( current_user_can( 'remove_users' ) ) {
276 $actions['remove'] = __( 'Remove' );
277 }
278 } else {
279 if ( current_user_can( 'delete_users' ) ) {
280 $actions['delete'] = __( 'Delete' );
281 }
282 }
283
284 // Add a password reset link to the bulk actions dropdown.
285 if ( current_user_can( 'edit_users' ) ) {
286 $actions['resetpassword'] = __( 'Send password reset' );
287 }
288
289 return $actions;
290 }
291
292 /**
293 * Outputs the controls to allow user roles to be changed in bulk.
294 *
295 * @since 3.1.0
296 *
297 * @param string $which Whether this is being invoked above ("top")
298 * or below the table ("bottom").
299 */
300 protected function extra_tablenav( $which ) {
301 $id = 'bottom' === $which ? 'new_role2' : 'new_role';
302 $button_id = 'bottom' === $which ? 'changeit2' : 'changeit';
303 ?>
304 <div class="alignleft actions">
305 <?php if ( current_user_can( 'promote_users' ) && $this->has_items() ) : ?>
306 <label class="screen-reader-text" for="<?php echo $id; ?>">
307 <?php
308 /* translators: Hidden accessibility text. */
309 _e( 'Change role to…' );
310 ?>
311 </label>
312 <select name="<?php echo $id; ?>" id="<?php echo $id; ?>">
313 <option value=""><?php _e( 'Change role to…' ); ?></option>
314 <?php wp_dropdown_roles(); ?>
315 <option value="none"><?php _e( '— No role for this site —' ); ?></option>
316 </select>
317 <?php
318 submit_button( __( 'Change' ), '', $button_id, false );
319 endif;
320
321 /**
322 * Fires just before the closing div containing the bulk role-change controls
323 * in the Users list table.
324 *
325 * @since 3.5.0
326 * @since 4.6.0 The `$which` parameter was added.
327 *
328 * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
329 */
330 do_action( 'restrict_manage_users', $which );
331 ?>
332 </div>
333 <?php
334 /**
335 * Fires immediately following the closing "actions" div in the tablenav for the users
336 * list table.
337 *
338 * @since 4.9.0
339 *
340 * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
341 */
342 do_action( 'manage_users_extra_tablenav', $which );
343 }
344
345 /**
346 * Captures the bulk action required, and return it.
347 *
348 * Overridden from the base class implementation to capture
349 * the role change drop-down.
350 *
351 * @since 3.1.0
352 *
353 * @return string The bulk action required.
354 */
355 public function current_action() {
356 if ( isset( $_REQUEST['changeit'] ) ) {
357 return 'promote';
358 }
359
360 return parent::current_action();
361 }
362
363 /**
364 * Gets a list of columns for the list table.
365 *
366 * @since 3.1.0
367 *
368 * @return string[] Array of column titles keyed by their column name.
369 */
370 public function get_columns() {
371 $columns = array(
372 'cb' => '<input type="checkbox" />',
373 'username' => __( 'Username' ),
374 'name' => __( 'Name' ),
375 'email' => __( 'Email' ),
376 'role' => __( 'Role' ),
377 'posts' => _x( 'Posts', 'post type general name' ),
378 );
379
380 if ( $this->is_site_users ) {
381 unset( $columns['posts'] );
382 }
383
384 return $columns;
385 }
386
387 /**
388 * Gets a list of sortable columns for the list table.
389 *
390 * @since 3.1.0
391 *
392 * @return array Array of sortable columns.
393 */
394 protected function get_sortable_columns() {
395 $columns = array(
396 'username' => array( 'login', false, __( 'Username' ), __( 'Table ordered by Username.' ), 'asc' ),
397 'email' => array( 'email', false, __( 'E-mail' ), __( 'Table ordered by E-mail.' ) ),
398 );
399
400 return $columns;
401 }
402
403 /**
404 * Generates the list table rows.
405 *
406 * @since 3.1.0
407 */
408 public function display_rows() {
409 // Query the post counts for this page.
410 if ( ! $this->is_site_users ) {
411 $post_counts = count_many_users_posts( array_keys( $this->items ) );
412 }
413
414 foreach ( $this->items as $userid => $user_object ) {
415 echo "\n\t" . $this->single_row( $user_object, '', '', isset( $post_counts ) ? $post_counts[ $userid ] : 0 );
416 }
417 }
418
419 /**
420 * Generates HTML for a single row on the users.php admin panel.
421 *
422 * @since 3.1.0
423 * @since 4.2.0 The `$style` parameter was deprecated.
424 * @since 4.4.0 The `$role` parameter was deprecated.
425 *
426 * @param WP_User $user_object The current user object.
427 * @param string $style Deprecated. Not used.
428 * @param string $role Deprecated. Not used.
429 * @param int $numposts Optional. Post count to display for this user. Defaults
430 * to zero, as in, a new user has made zero posts.
431 * @return string Output for a single row.
432 */
433 public function single_row( $user_object, $style = '', $role = '', $numposts = 0 ) {
434 if ( ! ( $user_object instanceof WP_User ) ) {
435 $user_object = get_userdata( (int) $user_object );
436 }
437 $user_object->filter = 'display';
438 $email = $user_object->user_email;
439
440 if ( $this->is_site_users ) {
441 $url = "site-users.php?id={$this->site_id}&";
442 } else {
443 $url = 'users.php?';
444 }
445
446 $user_roles = $this->get_role_list( $user_object );
447
448 // Set up the hover actions for this user.
449 $actions = array();
450 $checkbox = '';
451 $super_admin = '';
452
453 if ( is_multisite() && current_user_can( 'manage_network_users' ) ) {
454 if ( in_array( $user_object->user_login, get_super_admins(), true ) ) {
455 $super_admin = ' — ' . __( 'Super Admin' );
456 }
457 }
458
459 // Check if the user for this row is editable.
460 if ( current_user_can( 'list_users' ) ) {
461 // Set up the user editing link.
462 $edit_link = esc_url(
463 add_query_arg(
464 'wp_http_referer',
465 urlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
466 get_edit_user_link( $user_object->ID )
467 )
468 );
469
470 if ( current_user_can( 'edit_user', $user_object->ID ) ) {
471 $edit = "<strong><a href=\"{$edit_link}\">{$user_object->user_login}</a>{$super_admin}</strong><br />";
472 $actions['edit'] = '<a href="' . $edit_link . '">' . __( 'Edit' ) . '</a>';
473 } else {
474 $edit = "<strong>{$user_object->user_login}{$super_admin}</strong><br />";
475 }
476
477 if ( ! is_multisite()
478 && get_current_user_id() !== $user_object->ID
479 && current_user_can( 'delete_user', $user_object->ID )
480 ) {
481 $actions['delete'] = "<a class='submitdelete' href='" . wp_nonce_url( "users.php?action=delete&user=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Delete' ) . '</a>';
482 }
483
484 if ( is_multisite()
485 && current_user_can( 'remove_user', $user_object->ID )
486 ) {
487 $actions['remove'] = "<a class='submitdelete' href='" . wp_nonce_url( $url . "action=remove&user=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Remove' ) . '</a>';
488 }
489
490 // Add a link to the user's author archive, if not empty.
491 $author_posts_url = get_author_posts_url( $user_object->ID );
492 if ( $author_posts_url ) {
493 $actions['view'] = sprintf(
494 '<a href="%s" aria-label="%s">%s</a>',
495 esc_url( $author_posts_url ),
496 /* translators: %s: Author's display name. */
497 esc_attr( sprintf( __( 'View posts by %s' ), $user_object->display_name ) ),
498 __( 'View' )
499 );
500 }
501
502 // Add a link to send the user a reset password link by email.
503 if ( get_current_user_id() !== $user_object->ID
504 && current_user_can( 'edit_user', $user_object->ID )
505 && true === wp_is_password_reset_allowed_for_user( $user_object )
506 ) {
507 $actions['resetpassword'] = "<a class='resetpassword' href='" . wp_nonce_url( "users.php?action=resetpassword&users=$user_object->ID", 'bulk-users' ) . "'>" . __( 'Send password reset' ) . '</a>';
508 }
509
510 /**
511 * Filters the action links displayed under each user in the Users list table.
512 *
513 * @since 2.8.0
514 *
515 * @param string[] $actions An array of action links to be displayed.
516 * Default 'Edit', 'Delete' for single site, and
517 * 'Edit', 'Remove' for Multisite.
518 * @param WP_User $user_object WP_User object for the currently listed user.
519 */
520 $actions = apply_filters( 'user_row_actions', $actions, $user_object );
521
522 // Role classes.
523 $role_classes = esc_attr( implode( ' ', array_keys( $user_roles ) ) );
524
525 // Set up the checkbox (because the user is editable, otherwise it's empty).
526 $checkbox = sprintf(
527 '<input type="checkbox" name="users[]" id="user_%1$s" class="%2$s" value="%1$s" />' .
528 '<label for="user_%1$s"><span class="screen-reader-text">%3$s</span></label>',
529 $user_object->ID,
530 $role_classes,
531 /* translators: Hidden accessibility text. %s: User login. */
532 sprintf( __( 'Select %s' ), $user_object->user_login )
533 );
534
535 } else {
536 $edit = "<strong>{$user_object->user_login}{$super_admin}</strong>";
537 }
538
539 $avatar = get_avatar( $user_object->ID, 32 );
540
541 // Comma-separated list of user roles.
542 $roles_list = implode( ', ', $user_roles );
543
544 $row = "<tr id='user-$user_object->ID'>";
545
546 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
547
548 foreach ( $columns as $column_name => $column_display_name ) {
549 $classes = "$column_name column-$column_name";
550 if ( $primary === $column_name ) {
551 $classes .= ' has-row-actions column-primary';
552 }
553 if ( 'posts' === $column_name ) {
554 $classes .= ' num'; // Special case for that column.
555 }
556
557 if ( in_array( $column_name, $hidden, true ) ) {
558 $classes .= ' hidden';
559 }
560
561 $data = 'data-colname="' . esc_attr( wp_strip_all_tags( $column_display_name ) ) . '"';
562
563 $attributes = "class='$classes' $data";
564
565 if ( 'cb' === $column_name ) {
566 $row .= "<th scope='row' class='check-column'>$checkbox</th>";
567 } else {
568 $row .= "<td $attributes>";
569 switch ( $column_name ) {
570 case 'username':
571 $row .= "$avatar $edit";
572 break;
573 case 'name':
574 if ( $user_object->first_name && $user_object->last_name ) {
575 $row .= sprintf(
576 /* translators: 1: User's first name, 2: Last name. */
577 _x( '%1$s %2$s', 'Display name based on first name and last name' ),
578 $user_object->first_name,
579 $user_object->last_name
580 );
581 } elseif ( $user_object->first_name ) {
582 $row .= $user_object->first_name;
583 } elseif ( $user_object->last_name ) {
584 $row .= $user_object->last_name;
585 } else {
586 $row .= sprintf(
587 '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
588 /* translators: Hidden accessibility text. */
589 _x( 'Unknown', 'name' )
590 );
591 }
592 break;
593 case 'email':
594 $row .= "<a href='" . esc_url( "mailto:$email" ) . "'>$email</a>";
595 break;
596 case 'role':
597 $row .= esc_html( $roles_list );
598 break;
599 case 'posts':
600 if ( $numposts > 0 ) {
601 $row .= sprintf(
602 '<a href="%s" class="edit"><span aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a>',
603 "edit.php?author={$user_object->ID}",
604 $numposts,
605 sprintf(
606 /* translators: Hidden accessibility text. %s: Number of posts. */
607 _n( '%s post by this author', '%s posts by this author', $numposts ),
608 number_format_i18n( $numposts )
609 )
610 );
611 } else {
612 $row .= 0;
613 }
614 break;
615 default:
616 /**
617 * Filters the display output of custom columns in the Users list table.
618 *
619 * @since 2.8.0
620 *
621 * @param string $output Custom column output. Default empty.
622 * @param string $column_name Column name.
623 * @param int $user_id ID of the currently-listed user.
624 */
625 $row .= apply_filters( 'manage_users_custom_column', '', $column_name, $user_object->ID );
626 }
627
628 if ( $primary === $column_name ) {
629 $row .= $this->row_actions( $actions );
630 }
631 $row .= '</td>';
632 }
633 }
634 $row .= '</tr>';
635
636 return $row;
637 }
638
639 /**
640 * Gets the name of the default primary column.
641 *
642 * @since 4.3.0
643 *
644 * @return string Name of the default primary column, in this case, 'username'.
645 */
646 protected function get_default_primary_column_name() {
647 return 'username';
648 }
649
650 /**
651 * Returns an array of translated user role names for a given user object.
652 *
653 * @since 4.4.0
654 *
655 * @param WP_User $user_object The WP_User object.
656 * @return string[] An array of user role names keyed by role.
657 */
658 protected function get_role_list( $user_object ) {
659 $wp_roles = wp_roles();
660
661 $role_list = array();
662
663 foreach ( $user_object->roles as $role ) {
664 if ( isset( $wp_roles->role_names[ $role ] ) ) {
665 $role_list[ $role ] = translate_user_role( $wp_roles->role_names[ $role ] );
666 }
667 }
668
669 if ( empty( $role_list ) ) {
670 $role_list['none'] = _x( 'None', 'no user roles' );
671 }
672
673 /**
674 * Filters the returned array of translated role names for a user.
675 *
676 * @since 4.4.0
677 *
678 * @param string[] $role_list An array of translated user role names keyed by role.
679 * @param WP_User $user_object A WP_User object.
680 */
681 return apply_filters( 'get_role_list', $role_list, $user_object );
682 }
683}
684