1<?php
2/**
3 * List Table API: WP_MS_Sites_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 sites in a list table for the network admin.
12 *
13 * @since 3.1.0
14 *
15 * @see WP_List_Table
16 */
17class WP_MS_Sites_List_Table extends WP_List_Table {
18
19 /**
20 * Site status list.
21 *
22 * @since 4.3.0
23 * @var array
24 */
25 public $status_list;
26
27 /**
28 * Constructor.
29 *
30 * @since 3.1.0
31 *
32 * @see WP_List_Table::__construct() for more information on default arguments.
33 *
34 * @param array $args An associative array of arguments.
35 */
36 public function __construct( $args = array() ) {
37 $this->status_list = array(
38 'archived' => array( 'site-archived', __( 'Archived' ) ),
39 'spam' => array( 'site-spammed', _x( 'Spam', 'site' ) ),
40 'deleted' => array( 'site-deleted', __( 'Flagged for Deletion' ) ),
41 'mature' => array( 'site-mature', __( 'Mature' ) ),
42 );
43
44 parent::__construct(
45 array(
46 'plural' => 'sites',
47 'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
48 )
49 );
50 }
51
52 /**
53 * @return bool
54 */
55 public function ajax_user_can() {
56 return current_user_can( 'manage_sites' );
57 }
58
59 /**
60 * Prepares the list of sites for display.
61 *
62 * @since 3.1.0
63 *
64 * @global string $mode List table view mode.
65 * @global string $s
66 * @global wpdb $wpdb WordPress database abstraction object.
67 */
68 public function prepare_items() {
69 global $mode, $s, $wpdb;
70
71 if ( ! empty( $_REQUEST['mode'] ) ) {
72 $mode = 'excerpt' === $_REQUEST['mode'] ? 'excerpt' : 'list';
73 set_user_setting( 'sites_list_mode', $mode );
74 } else {
75 $mode = get_user_setting( 'sites_list_mode', 'list' );
76 }
77
78 $per_page = $this->get_items_per_page( 'sites_network_per_page' );
79
80 $pagenum = $this->get_pagenum();
81
82 $s = isset( $_REQUEST['s'] ) ? wp_unslash( trim( $_REQUEST['s'] ) ) : '';
83 $wild = '';
84 if ( str_contains( $s, '*' ) ) {
85 $wild = '*';
86 $s = trim( $s, '*' );
87 }
88
89 /*
90 * If the network is large and a search is not being performed, show only
91 * the latest sites with no paging in order to avoid expensive count queries.
92 */
93 if ( ! $s && wp_is_large_network() ) {
94 if ( ! isset( $_REQUEST['orderby'] ) ) {
95 $_GET['orderby'] = '';
96 $_REQUEST['orderby'] = '';
97 }
98 if ( ! isset( $_REQUEST['order'] ) ) {
99 $_GET['order'] = 'DESC';
100 $_REQUEST['order'] = 'DESC';
101 }
102 }
103
104 $args = array(
105 'number' => (int) $per_page,
106 'offset' => (int) ( ( $pagenum - 1 ) * $per_page ),
107 'network_id' => get_current_network_id(),
108 );
109
110 if ( empty( $s ) ) {
111 // Nothing to do.
112 } elseif ( preg_match( '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $s )
113 || preg_match( '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.?$/', $s )
114 || preg_match( '/^[0-9]{1,3}\.[0-9]{1,3}\.?$/', $s )
115 || preg_match( '/^[0-9]{1,3}\.$/', $s )
116 ) {
117 // IPv4 address.
118 $reg_blog_ids = $wpdb->get_col(
119 $wpdb->prepare(
120 "SELECT blog_id FROM {$wpdb->registration_log} WHERE {$wpdb->registration_log}.IP LIKE %s",
121 $wpdb->esc_like( $s ) . ( ! empty( $wild ) ? '%' : '' )
122 )
123 );
124
125 if ( $reg_blog_ids ) {
126 $args['site__in'] = $reg_blog_ids;
127 }
128 } elseif ( is_numeric( $s ) && empty( $wild ) ) {
129 $args['ID'] = $s;
130 } else {
131 $args['search'] = $s;
132
133 if ( ! is_subdomain_install() ) {
134 $args['search_columns'] = array( 'path' );
135 }
136 }
137
138 $order_by = isset( $_REQUEST['orderby'] ) ? $_REQUEST['orderby'] : '';
139 if ( 'registered' === $order_by ) {
140 // 'registered' is a valid field name.
141 } elseif ( 'lastupdated' === $order_by ) {
142 $order_by = 'last_updated';
143 } elseif ( 'blogname' === $order_by ) {
144 if ( is_subdomain_install() ) {
145 $order_by = 'domain';
146 } else {
147 $order_by = 'path';
148 }
149 } elseif ( 'blog_id' === $order_by ) {
150 $order_by = 'id';
151 } elseif ( ! $order_by ) {
152 $order_by = false;
153 }
154
155 $args['orderby'] = $order_by;
156
157 if ( $order_by ) {
158 $args['order'] = ( isset( $_REQUEST['order'] ) && 'DESC' === strtoupper( $_REQUEST['order'] ) ) ? 'DESC' : 'ASC';
159 }
160
161 if ( wp_is_large_network() ) {
162 $args['no_found_rows'] = true;
163 } else {
164 $args['no_found_rows'] = false;
165 }
166
167 // Take into account the role the user has selected.
168 $status = isset( $_REQUEST['status'] ) ? wp_unslash( trim( $_REQUEST['status'] ) ) : '';
169 if ( in_array( $status, array( 'public', 'archived', 'mature', 'spam', 'deleted' ), true ) ) {
170 $args[ $status ] = 1;
171 }
172
173 /**
174 * Filters the arguments for the site query in the sites list table.
175 *
176 * @since 4.6.0
177 *
178 * @param array $args An array of get_sites() arguments.
179 */
180 $args = apply_filters( 'ms_sites_list_table_query_args', $args );
181
182 $_sites = get_sites( $args );
183 if ( is_array( $_sites ) ) {
184 update_site_cache( $_sites );
185
186 $this->items = array_slice( $_sites, 0, $per_page );
187 }
188
189 $total_sites = get_sites(
190 array_merge(
191 $args,
192 array(
193 'count' => true,
194 'offset' => 0,
195 'number' => 0,
196 )
197 )
198 );
199
200 $this->set_pagination_args(
201 array(
202 'total_items' => $total_sites,
203 'per_page' => $per_page,
204 )
205 );
206 }
207
208 /**
209 */
210 public function no_items() {
211 _e( 'No sites found.' );
212 }
213
214 /**
215 * Gets links to filter sites by status.
216 *
217 * @since 5.3.0
218 *
219 * @return array
220 */
221 protected function get_views() {
222 $counts = wp_count_sites();
223
224 $statuses = array(
225 /* translators: %s: Number of sites. */
226 'all' => _nx_noop(
227 'All <span class="count">(%s)</span>',
228 'All <span class="count">(%s)</span>',
229 'sites'
230 ),
231
232 /* translators: %s: Number of sites. */
233 'public' => _n_noop(
234 'Public <span class="count">(%s)</span>',
235 'Public <span class="count">(%s)</span>'
236 ),
237
238 /* translators: %s: Number of sites. */
239 'archived' => _n_noop(
240 'Archived <span class="count">(%s)</span>',
241 'Archived <span class="count">(%s)</span>'
242 ),
243
244 /* translators: %s: Number of sites. */
245 'mature' => _n_noop(
246 'Mature <span class="count">(%s)</span>',
247 'Mature <span class="count">(%s)</span>'
248 ),
249
250 /* translators: %s: Number of sites. */
251 'spam' => _nx_noop(
252 'Spam <span class="count">(%s)</span>',
253 'Spam <span class="count">(%s)</span>',
254 'sites'
255 ),
256
257 /* translators: %s: Number of sites. */
258 'deleted' => _n_noop(
259 'Flagged for Deletion <span class="count">(%s)</span>',
260 'Flagged for Deletion <span class="count">(%s)</span>'
261 ),
262 );
263
264 $view_links = array();
265 $requested_status = isset( $_REQUEST['status'] ) ? wp_unslash( trim( $_REQUEST['status'] ) ) : '';
266 $url = 'sites.php';
267
268 foreach ( $statuses as $status => $label_count ) {
269 if ( (int) $counts[ $status ] > 0 ) {
270 $label = sprintf(
271 translate_nooped_plural( $label_count, $counts[ $status ] ),
272 number_format_i18n( $counts[ $status ] )
273 );
274
275 $full_url = 'all' === $status ? $url : add_query_arg( 'status', $status, $url );
276
277 $view_links[ $status ] = array(
278 'url' => esc_url( $full_url ),
279 'label' => $label,
280 'current' => $requested_status === $status || ( '' === $requested_status && 'all' === $status ),
281 );
282 }
283 }
284
285 return $this->get_views_links( $view_links );
286 }
287
288 /**
289 * @return array
290 */
291 protected function get_bulk_actions() {
292 $actions = array();
293 if ( current_user_can( 'delete_sites' ) ) {
294 $actions['delete'] = __( 'Delete' );
295 }
296 $actions['spam'] = _x( 'Mark as spam', 'site' );
297 $actions['notspam'] = _x( 'Not spam', 'site' );
298
299 return $actions;
300 }
301
302 /**
303 * @global string $mode List table view mode.
304 *
305 * @param string $which The location of the pagination nav markup: Either 'top' or 'bottom'.
306 */
307 protected function pagination( $which ) {
308 global $mode;
309
310 parent::pagination( $which );
311
312 if ( 'top' === $which ) {
313 $this->view_switcher( $mode );
314 }
315 }
316
317 /**
318 * Displays extra controls between bulk actions and pagination.
319 *
320 * @since 5.3.0
321 *
322 * @param string $which The location of the extra table nav markup: Either 'top' or 'bottom'.
323 */
324 protected function extra_tablenav( $which ) {
325 ?>
326 <div class="alignleft actions">
327 <?php
328 if ( 'top' === $which ) {
329 ob_start();
330
331 /**
332 * Fires before the Filter button on the MS sites list table.
333 *
334 * @since 5.3.0
335 *
336 * @param string $which The location of the extra table nav markup: Either 'top' or 'bottom'.
337 */
338 do_action( 'restrict_manage_sites', $which );
339
340 $output = ob_get_clean();
341
342 if ( ! empty( $output ) ) {
343 echo $output;
344 submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'site-query-submit' ) );
345 }
346 }
347 ?>
348 </div>
349 <?php
350 /**
351 * Fires immediately following the closing "actions" div in the tablenav for the
352 * MS sites list table.
353 *
354 * @since 5.3.0
355 *
356 * @param string $which The location of the extra table nav markup: Either 'top' or 'bottom'.
357 */
358 do_action( 'manage_sites_extra_tablenav', $which );
359 }
360
361 /**
362 * @return string[] Array of column titles keyed by their column name.
363 */
364 public function get_columns() {
365 $sites_columns = array(
366 'cb' => '<input type="checkbox" />',
367 'blogname' => __( 'URL' ),
368 'lastupdated' => __( 'Last Updated' ),
369 'registered' => _x( 'Registered', 'site' ),
370 'users' => __( 'Users' ),
371 );
372
373 if ( has_filter( 'wpmublogsaction' ) ) {
374 $sites_columns['plugins'] = __( 'Actions' );
375 }
376
377 /**
378 * Filters the displayed site columns in Sites list table.
379 *
380 * @since MU (3.0.0)
381 *
382 * @param string[] $sites_columns An array of displayed site columns. Default 'cb',
383 * 'blogname', 'lastupdated', 'registered', 'users'.
384 */
385 return apply_filters( 'wpmu_blogs_columns', $sites_columns );
386 }
387
388 /**
389 * @return array
390 */
391 protected function get_sortable_columns() {
392
393 if ( is_subdomain_install() ) {
394 $blogname_abbr = __( 'Domain' );
395 $blogname_orderby_text = __( 'Table ordered by Site Domain Name.' );
396 } else {
397 $blogname_abbr = __( 'Path' );
398 $blogname_orderby_text = __( 'Table ordered by Site Path.' );
399 }
400
401 return array(
402 'blogname' => array( 'blogname', false, $blogname_abbr, $blogname_orderby_text ),
403 'lastupdated' => array( 'lastupdated', true, __( 'Last Updated' ), __( 'Table ordered by Last Updated.' ) ),
404 'registered' => array( 'blog_id', true, _x( 'Registered', 'site' ), __( 'Table ordered by Site Registered Date.' ), 'desc' ),
405 );
406 }
407
408 /**
409 * Handles the checkbox column output.
410 *
411 * @since 4.3.0
412 * @since 5.9.0 Renamed `$blog` to `$item` to match parent class for PHP 8 named parameter support.
413 *
414 * @param array $item Current site.
415 */
416 public function column_cb( $item ) {
417 // Restores the more descriptive, specific name for use within this method.
418 $blog = $item;
419
420 if ( ! is_main_site( $blog['blog_id'] ) ) :
421 $blogname = untrailingslashit( $blog['domain'] . $blog['path'] );
422 ?>
423 <input type="checkbox" id="blog_<?php echo $blog['blog_id']; ?>" name="allblogs[]" value="<?php echo esc_attr( $blog['blog_id'] ); ?>" />
424 <label for="blog_<?php echo $blog['blog_id']; ?>">
425 <span class="screen-reader-text">
426 <?php
427 /* translators: %s: Site URL. */
428 printf( __( 'Select %s' ), $blogname );
429 ?>
430 </span>
431 </label>
432 <?php
433 endif;
434 }
435
436 /**
437 * Handles the ID column output.
438 *
439 * @since 4.4.0
440 *
441 * @param array $blog Current site.
442 */
443 public function column_id( $blog ) {
444 echo $blog['blog_id'];
445 }
446
447 /**
448 * Handles the site name column output.
449 *
450 * @since 4.3.0
451 *
452 * @global string $mode List table view mode.
453 *
454 * @param array $blog Current site.
455 */
456 public function column_blogname( $blog ) {
457 global $mode;
458
459 $blogname = untrailingslashit( $blog['domain'] . $blog['path'] );
460
461 ?>
462 <strong>
463 <?php
464 printf(
465 '<a href="%1$s" class="edit">%2$s</a>',
466 esc_url( network_admin_url( 'site-info.php?id=' . $blog['blog_id'] ) ),
467 $blogname
468 );
469
470 $this->site_states( $blog );
471 ?>
472 </strong>
473 <?php
474 if ( 'list' !== $mode ) {
475 switch_to_blog( $blog['blog_id'] );
476 echo '<p>';
477 printf(
478 /* translators: 1: Site title, 2: Site tagline. */
479 __( '%1$s – %2$s' ),
480 get_option( 'blogname' ),
481 '<em>' . get_option( 'blogdescription' ) . '</em>'
482 );
483 echo '</p>';
484 restore_current_blog();
485 }
486 }
487
488 /**
489 * Handles the lastupdated column output.
490 *
491 * @since 4.3.0
492 *
493 * @global string $mode List table view mode.
494 *
495 * @param array $blog Current site.
496 */
497 public function column_lastupdated( $blog ) {
498 global $mode;
499
500 if ( 'list' === $mode ) {
501 $date = __( 'Y/m/d' );
502 } else {
503 $date = __( 'Y/m/d g:i:s a' );
504 }
505
506 if ( '0000-00-00 00:00:00' === $blog['last_updated'] ) {
507 _e( 'Never' );
508 } else {
509 echo mysql2date( $date, $blog['last_updated'] );
510 }
511 }
512
513 /**
514 * Handles the registered column output.
515 *
516 * @since 4.3.0
517 *
518 * @global string $mode List table view mode.
519 *
520 * @param array $blog Current site.
521 */
522 public function column_registered( $blog ) {
523 global $mode;
524
525 if ( 'list' === $mode ) {
526 $date = __( 'Y/m/d' );
527 } else {
528 $date = __( 'Y/m/d g:i:s a' );
529 }
530
531 if ( '0000-00-00 00:00:00' === $blog['registered'] ) {
532 echo '—';
533 } else {
534 echo mysql2date( $date, $blog['registered'] );
535 }
536 }
537
538 /**
539 * Handles the users column output.
540 *
541 * @since 4.3.0
542 *
543 * @param array $blog Current site.
544 */
545 public function column_users( $blog ) {
546 $user_count = wp_cache_get( $blog['blog_id'] . '_user_count', 'blog-details' );
547 if ( ! $user_count ) {
548 $blog_users = new WP_User_Query(
549 array(
550 'blog_id' => $blog['blog_id'],
551 'fields' => 'ID',
552 'number' => 1,
553 'count_total' => true,
554 )
555 );
556 $user_count = $blog_users->get_total();
557 wp_cache_set( $blog['blog_id'] . '_user_count', $user_count, 'blog-details', 12 * HOUR_IN_SECONDS );
558 }
559
560 printf(
561 '<a href="%1$s">%2$s</a>',
562 esc_url( network_admin_url( 'site-users.php?id=' . $blog['blog_id'] ) ),
563 number_format_i18n( $user_count )
564 );
565 }
566
567 /**
568 * Handles the plugins column output.
569 *
570 * @since 4.3.0
571 *
572 * @param array $blog Current site.
573 */
574 public function column_plugins( $blog ) {
575 if ( has_filter( 'wpmublogsaction' ) ) {
576 /**
577 * Fires inside the auxiliary 'Actions' column of the Sites list table.
578 *
579 * By default this column is hidden unless something is hooked to the action.
580 *
581 * @since MU (3.0.0)
582 *
583 * @param int $blog_id The site ID.
584 */
585 do_action( 'wpmublogsaction', $blog['blog_id'] );
586 }
587 }
588
589 /**
590 * Handles output for the default column.
591 *
592 * @since 4.3.0
593 * @since 5.9.0 Renamed `$blog` to `$item` to match parent class for PHP 8 named parameter support.
594 *
595 * @param array $item Current site.
596 * @param string $column_name Current column name.
597 */
598 public function column_default( $item, $column_name ) {
599 // Restores the more descriptive, specific name for use within this method.
600 $blog = $item;
601
602 /**
603 * Fires for each registered custom column in the Sites list table.
604 *
605 * @since 3.1.0
606 *
607 * @param string $column_name The name of the column to display.
608 * @param int $blog_id The site ID.
609 */
610 do_action( 'manage_sites_custom_column', $column_name, $blog['blog_id'] );
611 }
612
613 /**
614 * Generates the list table rows.
615 *
616 * @since 3.1.0
617 */
618 public function display_rows() {
619 foreach ( $this->items as $blog ) {
620 $blog = $blog->to_array();
621 $class = '';
622 reset( $this->status_list );
623
624 foreach ( $this->status_list as $status => $col ) {
625 if ( '1' === $blog[ $status ] ) {
626 $class = " class='{$col[0]}'";
627 }
628 }
629
630 echo "<tr{$class}>";
631
632 $this->single_row_columns( $blog );
633
634 echo '</tr>';
635 }
636 }
637
638 /**
639 * Determines whether to output comma-separated site states.
640 *
641 * @since 5.3.0
642 *
643 * @param array $site
644 */
645 protected function site_states( $site ) {
646 $site_states = array();
647
648 // $site is still an array, so get the object.
649 $_site = WP_Site::get_instance( $site['blog_id'] );
650
651 if ( is_main_site( $_site->id ) ) {
652 $site_states['main'] = __( 'Main' );
653 }
654
655 reset( $this->status_list );
656
657 $site_status = isset( $_REQUEST['status'] ) ? wp_unslash( trim( $_REQUEST['status'] ) ) : '';
658 foreach ( $this->status_list as $status => $col ) {
659 if ( '1' === $_site->{$status} && $site_status !== $status ) {
660 $site_states[ $col[0] ] = $col[1];
661 }
662 }
663
664 /**
665 * Filters the default site display states for items in the Sites list table.
666 *
667 * @since 5.3.0
668 *
669 * @param string[] $site_states An array of site states. Default 'Main',
670 * 'Archived', 'Mature', 'Spam', 'Flagged for Deletion'.
671 * @param WP_Site $site The current site object.
672 */
673 $site_states = apply_filters( 'display_site_states', $site_states, $_site );
674
675 if ( ! empty( $site_states ) ) {
676 $state_count = count( $site_states );
677
678 $i = 0;
679
680 echo ' — ';
681
682 foreach ( $site_states as $state ) {
683 ++$i;
684
685 $separator = ( $i < $state_count ) ? ', ' : '';
686
687 echo "<span class='post-state'>{$state}{$separator}</span>";
688 }
689 }
690 }
691
692 /**
693 * Gets the name of the default primary column.
694 *
695 * @since 4.3.0
696 *
697 * @return string Name of the default primary column, in this case, 'blogname'.
698 */
699 protected function get_default_primary_column_name() {
700 return 'blogname';
701 }
702
703 /**
704 * Generates and displays row action links.
705 *
706 * @since 4.3.0
707 * @since 5.9.0 Renamed `$blog` to `$item` to match parent class for PHP 8 named parameter support.
708 *
709 * @param array $item Site being acted upon.
710 * @param string $column_name Current column name.
711 * @param string $primary Primary column name.
712 * @return string Row actions output for sites in Multisite, or an empty string
713 * if the current column is not the primary column.
714 */
715 protected function handle_row_actions( $item, $column_name, $primary ) {
716 if ( $primary !== $column_name ) {
717 return '';
718 }
719
720 // Restores the more descriptive, specific name for use within this method.
721 $blog = $item;
722
723 $blogname = untrailingslashit( $blog['domain'] . $blog['path'] );
724
725 // Preordered.
726 $actions = array(
727 'edit' => '',
728 'backend' => '',
729 'activate' => '',
730 'deactivate' => '',
731 'archive' => '',
732 'unarchive' => '',
733 'spam' => '',
734 'unspam' => '',
735 'delete' => '',
736 'visit' => '',
737 );
738
739 $actions['edit'] = sprintf(
740 '<a href="%1$s">%2$s</a>',
741 esc_url( network_admin_url( 'site-info.php?id=' . $blog['blog_id'] ) ),
742 __( 'Edit' )
743 );
744
745 $actions['backend'] = sprintf(
746 '<a href="%1$s" class="edit">%2$s</a>',
747 esc_url( get_admin_url( $blog['blog_id'] ) ),
748 __( 'Dashboard' )
749 );
750
751 if ( ! is_main_site( $blog['blog_id'] ) ) {
752 if ( '1' === $blog['deleted'] ) {
753 $actions['activate'] = sprintf(
754 '<a href="%1$s">%2$s</a>',
755 esc_url(
756 wp_nonce_url(
757 network_admin_url( 'sites.php?action=confirm&action2=activateblog&id=' . $blog['blog_id'] ),
758 'activateblog_' . $blog['blog_id']
759 )
760 ),
761 _x( 'Remove Deletion Flag', 'site' )
762 );
763 } else {
764 $actions['deactivate'] = sprintf(
765 '<a href="%1$s">%2$s</a>',
766 esc_url(
767 wp_nonce_url(
768 network_admin_url( 'sites.php?action=confirm&action2=deactivateblog&id=' . $blog['blog_id'] ),
769 'deactivateblog_' . $blog['blog_id']
770 )
771 ),
772 __( 'Flag for Deletion' )
773 );
774 }
775
776 if ( '1' === $blog['archived'] ) {
777 $actions['unarchive'] = sprintf(
778 '<a href="%1$s">%2$s</a>',
779 esc_url(
780 wp_nonce_url(
781 network_admin_url( 'sites.php?action=confirm&action2=unarchiveblog&id=' . $blog['blog_id'] ),
782 'unarchiveblog_' . $blog['blog_id']
783 )
784 ),
785 __( 'Unarchive' )
786 );
787 } else {
788 $actions['archive'] = sprintf(
789 '<a href="%1$s">%2$s</a>',
790 esc_url(
791 wp_nonce_url(
792 network_admin_url( 'sites.php?action=confirm&action2=archiveblog&id=' . $blog['blog_id'] ),
793 'archiveblog_' . $blog['blog_id']
794 )
795 ),
796 _x( 'Archive', 'verb; site' )
797 );
798 }
799
800 if ( '1' === $blog['spam'] ) {
801 $actions['unspam'] = sprintf(
802 '<a href="%1$s">%2$s</a>',
803 esc_url(
804 wp_nonce_url(
805 network_admin_url( 'sites.php?action=confirm&action2=unspamblog&id=' . $blog['blog_id'] ),
806 'unspamblog_' . $blog['blog_id']
807 )
808 ),
809 _x( 'Not Spam', 'site' )
810 );
811 } else {
812 $actions['spam'] = sprintf(
813 '<a href="%1$s">%2$s</a>',
814 esc_url(
815 wp_nonce_url(
816 network_admin_url( 'sites.php?action=confirm&action2=spamblog&id=' . $blog['blog_id'] ),
817 'spamblog_' . $blog['blog_id']
818 )
819 ),
820 _x( 'Spam', 'site' )
821 );
822 }
823
824 if ( current_user_can( 'delete_site', $blog['blog_id'] ) ) {
825 $actions['delete'] = sprintf(
826 '<a href="%1$s">%2$s</a>',
827 esc_url(
828 wp_nonce_url(
829 network_admin_url( 'sites.php?action=confirm&action2=deleteblog&id=' . $blog['blog_id'] ),
830 'deleteblog_' . $blog['blog_id']
831 )
832 ),
833 __( 'Delete Permanently' )
834 );
835 }
836 }
837
838 $actions['visit'] = sprintf(
839 '<a href="%1$s" rel="bookmark">%2$s</a>',
840 esc_url( get_home_url( $blog['blog_id'], '/' ) ),
841 __( 'Visit' )
842 );
843
844 /**
845 * Filters the action links displayed for each site in the Sites list table.
846 *
847 * The 'Edit', 'Dashboard', 'Delete Permanently', and 'Visit' links are displayed by
848 * default for each site. The site's status determines whether to show the
849 * 'Remove Deletion Flag' or 'Flag for Deletion' link, 'Unarchive' or 'Archive' links, and
850 * 'Not Spam' or 'Spam' link for each site.
851 *
852 * @since 3.1.0
853 *
854 * @param string[] $actions An array of action links to be displayed.
855 * @param int $blog_id The site ID.
856 * @param string $blogname Site path, formatted depending on whether it is a sub-domain
857 * or subdirectory multisite installation.
858 */
859 $actions = apply_filters( 'manage_sites_action_links', array_filter( $actions ), $blog['blog_id'], $blogname );
860
861 return $this->row_actions( $actions );
862 }
863}
864