Wordpress 4.6
[autoinstalls/wordpress.git] / wp-admin / includes / class-wp-ms-themes-list-table.php
1 <?php
2 /**
3  * List Table API: WP_MS_Themes_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 themes in a list table for the network admin.
12  *
13  * @since 3.1.0
14  * @access private
15  *
16  * @see WP_List_Table
17  */
18 class WP_MS_Themes_List_Table extends WP_List_Table {
19
20         public $site_id;
21         public $is_site_themes;
22
23         private $has_items;
24
25         /**
26          * Constructor.
27          *
28          * @since 3.1.0
29          * @access public
30          *
31          * @see WP_List_Table::__construct() for more information on default arguments.
32          *
33          * @global string $status
34          * @global int    $page
35          *
36          * @param array $args An associative array of arguments.
37          */
38         public function __construct( $args = array() ) {
39                 global $status, $page;
40
41                 parent::__construct( array(
42                         'plural' => 'themes',
43                         'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
44                 ) );
45
46                 $status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all';
47                 if ( !in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken' ) ) )
48                         $status = 'all';
49
50                 $page = $this->get_pagenum();
51
52                 $this->is_site_themes = ( 'site-themes-network' === $this->screen->id ) ? true : false;
53
54                 if ( $this->is_site_themes )
55                         $this->site_id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0;
56         }
57
58         /**
59          *
60          * @return array
61          */
62         protected function get_table_classes() {
63                 // todo: remove and add CSS for .themes
64                 return array( 'widefat', 'plugins' );
65         }
66
67         /**
68          *
69          * @return bool
70          */
71         public function ajax_user_can() {
72                 if ( $this->is_site_themes )
73                         return current_user_can( 'manage_sites' );
74                 else
75                         return current_user_can( 'manage_network_themes' );
76         }
77
78         /**
79          *
80          * @global string $status
81          * @global array $totals
82          * @global int $page
83          * @global string $orderby
84          * @global string $order
85          * @global string $s
86          */
87         public function prepare_items() {
88                 global $status, $totals, $page, $orderby, $order, $s;
89
90                 wp_reset_vars( array( 'orderby', 'order', 's' ) );
91
92                 $themes = array(
93                         /**
94                          * Filters the full array of WP_Theme objects to list in the Multisite
95                          * themes list table.
96                          *
97                          * @since 3.1.0
98                          *
99                          * @param array $all An array of WP_Theme objects to display in the list table.
100                          */
101                         'all' => apply_filters( 'all_themes', wp_get_themes() ),
102                         'search' => array(),
103                         'enabled' => array(),
104                         'disabled' => array(),
105                         'upgrade' => array(),
106                         'broken' => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ),
107                 );
108
109                 if ( $this->is_site_themes ) {
110                         $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' );
111                         $allowed_where = 'site';
112                 } else {
113                         $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' );
114                         $allowed_where = 'network';
115                 }
116
117                 $maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current = get_site_transient( 'update_themes' );
118
119                 foreach ( (array) $themes['all'] as $key => $theme ) {
120                         if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) {
121                                 unset( $themes['all'][ $key ] );
122                                 continue;
123                         }
124
125                         if ( $maybe_update && isset( $current->response[ $key ] ) ) {
126                                 $themes['all'][ $key ]->update = true;
127                                 $themes['upgrade'][ $key ] = $themes['all'][ $key ];
128                         }
129
130                         $filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled';
131                         $themes[ $filter ][ $key ] = $themes['all'][ $key ];
132                 }
133
134                 if ( $s ) {
135                         $status = 'search';
136                         $themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) );
137                 }
138
139                 $totals = array();
140                 foreach ( $themes as $type => $list )
141                         $totals[ $type ] = count( $list );
142
143                 if ( empty( $themes[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) )
144                         $status = 'all';
145
146                 $this->items = $themes[ $status ];
147                 WP_Theme::sort_by_name( $this->items );
148
149                 $this->has_items = ! empty( $themes['all'] );
150                 $total_this_page = $totals[ $status ];
151
152                 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array(
153                         'totals' => $totals,
154                 ) );
155
156                 if ( $orderby ) {
157                         $orderby = ucfirst( $orderby );
158                         $order = strtoupper( $order );
159
160                         if ( $orderby === 'Name' ) {
161                                 if ( 'ASC' === $order ) {
162                                         $this->items = array_reverse( $this->items );
163                                 }
164                         } else {
165                                 uasort( $this->items, array( $this, '_order_callback' ) );
166                         }
167                 }
168
169                 $start = ( $page - 1 ) * $themes_per_page;
170
171                 if ( $total_this_page > $themes_per_page )
172                         $this->items = array_slice( $this->items, $start, $themes_per_page, true );
173
174                 $this->set_pagination_args( array(
175                         'total_items' => $total_this_page,
176                         'per_page' => $themes_per_page,
177                 ) );
178         }
179
180         /**
181          * @staticvar string $term
182          * @param WP_Theme $theme
183          * @return bool
184          */
185         public function _search_callback( $theme ) {
186                 static $term = null;
187                 if ( is_null( $term ) )
188                         $term = wp_unslash( $_REQUEST['s'] );
189
190                 foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) {
191                         // Don't mark up; Do translate.
192                         if ( false !== stripos( $theme->display( $field, false, true ), $term ) )
193                                 return true;
194                 }
195
196                 if ( false !== stripos( $theme->get_stylesheet(), $term ) )
197                         return true;
198
199                 if ( false !== stripos( $theme->get_template(), $term ) )
200                         return true;
201
202                 return false;
203         }
204
205         // Not used by any core columns.
206         /**
207          * @global string $orderby
208          * @global string $order
209          * @param array $theme_a
210          * @param array $theme_b
211          * @return int
212          */
213         public function _order_callback( $theme_a, $theme_b ) {
214                 global $orderby, $order;
215
216                 $a = $theme_a[ $orderby ];
217                 $b = $theme_b[ $orderby ];
218
219                 if ( $a == $b )
220                         return 0;
221
222                 if ( 'DESC' === $order )
223                         return ( $a < $b ) ? 1 : -1;
224                 else
225                         return ( $a < $b ) ? -1 : 1;
226         }
227
228         /**
229          * @access public
230          */
231         public function no_items() {
232                 if ( $this->has_items ) {
233                         _e( 'No themes found.' );
234                 } else {
235                         _e( 'You do not appear to have any themes available at this time.' );
236                 }
237         }
238
239         /**
240          *
241          * @return array
242          */
243         public function get_columns() {
244                 return array(
245                         'cb'          => '<input type="checkbox" />',
246                         'name'        => __( 'Theme' ),
247                         'description' => __( 'Description' ),
248                 );
249         }
250
251         /**
252          *
253          * @return array
254          */
255         protected function get_sortable_columns() {
256                 return array(
257                         'name'         => 'name',
258                 );
259         }
260
261         /**
262          * Gets the name of the primary column.
263          *
264          * @since 4.3.0
265          * @access protected
266          *
267          * @return string Unalterable name of the primary column name, in this case, 'name'.
268          */
269         protected function get_primary_column_name() {
270                 return 'name';
271         }
272
273         /**
274          *
275          * @global array $totals
276          * @global string $status
277          * @return array
278          */
279         protected function get_views() {
280                 global $totals, $status;
281
282                 $status_links = array();
283                 foreach ( $totals as $type => $count ) {
284                         if ( !$count )
285                                 continue;
286
287                         switch ( $type ) {
288                                 case 'all':
289                                         $text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'themes' );
290                                         break;
291                                 case 'enabled':
292                                         $text = _n( 'Enabled <span class="count">(%s)</span>', 'Enabled <span class="count">(%s)</span>', $count );
293                                         break;
294                                 case 'disabled':
295                                         $text = _n( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', $count );
296                                         break;
297                                 case 'upgrade':
298                                         $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
299                                         break;
300                                 case 'broken' :
301                                         $text = _n( 'Broken <span class="count">(%s)</span>', 'Broken <span class="count">(%s)</span>', $count );
302                                         break;
303                         }
304
305                         if ( $this->is_site_themes )
306                                 $url = 'site-themes.php?id=' . $this->site_id;
307                         else
308                                 $url = 'themes.php';
309
310                         if ( 'search' != $type ) {
311                                 $status_links[$type] = sprintf( "<a href='%s' %s>%s</a>",
312                                         esc_url( add_query_arg('theme_status', $type, $url) ),
313                                         ( $type === $status ) ? ' class="current"' : '',
314                                         sprintf( $text, number_format_i18n( $count ) )
315                                 );
316                         }
317                 }
318
319                 return $status_links;
320         }
321
322         /**
323          * @global string $status
324          *
325          * @return array
326          */
327         protected function get_bulk_actions() {
328                 global $status;
329
330                 $actions = array();
331                 if ( 'enabled' != $status )
332                         $actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' );
333                 if ( 'disabled' != $status )
334                         $actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' );
335                 if ( ! $this->is_site_themes ) {
336                         if ( current_user_can( 'update_themes' ) )
337                                 $actions['update-selected'] = __( 'Update' );
338                         if ( current_user_can( 'delete_themes' ) )
339                                 $actions['delete-selected'] = __( 'Delete' );
340                 }
341                 return $actions;
342         }
343
344         /**
345          * @access public
346          */
347         public function display_rows() {
348                 foreach ( $this->items as $theme )
349                         $this->single_row( $theme );
350         }
351
352         /**
353          * Handles the checkbox column output.
354          *
355          * @since 4.3.0
356          * @access public
357          *
358          * @param WP_Theme $theme The current WP_Theme object.
359          */
360         public function column_cb( $theme ) {
361                 $checkbox_id = 'checkbox_' . md5( $theme->get('Name') );
362                 ?>
363                 <input type="checkbox" name="checked[]" value="<?php echo esc_attr( $theme->get_stylesheet() ) ?>" id="<?php echo $checkbox_id ?>" />
364                 <label class="screen-reader-text" for="<?php echo $checkbox_id ?>" ><?php _e( 'Select' ) ?>  <?php echo $theme->display( 'Name' ) ?></label>
365                 <?php
366         }
367
368         /**
369          * Handles the name column output.
370          *
371          * @since 4.3.0
372          * @access public
373          *
374          * @global string $status
375          * @global int    $page
376          * @global string $s
377          *
378          * @param WP_Theme $theme The current WP_Theme object.
379          */
380         public function column_name( $theme ) {
381                 global $status, $page, $s;
382
383                 $context = $status;
384
385                 if ( $this->is_site_themes ) {
386                         $url = "site-themes.php?id={$this->site_id}&amp;";
387                         $allowed = $theme->is_allowed( 'site', $this->site_id );
388                 } else {
389                         $url = 'themes.php?';
390                         $allowed = $theme->is_allowed( 'network' );
391                 }
392
393                 // Pre-order.
394                 $actions = array(
395                         'enable' => '',
396                         'disable' => '',
397                         'edit' => '',
398                         'delete' => ''
399                 );
400
401                 $stylesheet = $theme->get_stylesheet();
402                 $theme_key = urlencode( $stylesheet );
403
404                 if ( ! $allowed ) {
405                         if ( ! $theme->errors() ) {
406                                 $url = add_query_arg( array(
407                                         'action' => 'enable',
408                                         'theme'  => $theme_key,
409                                         'paged'  => $page,
410                                         's'      => $s,
411                                 ), $url );
412
413                                 if ( $this->is_site_themes ) {
414                                         /* translators: %s: theme name */
415                                         $aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) );
416                                 } else {
417                                         /* translators: %s: theme name */
418                                         $aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) );
419                                 }
420
421                                 $actions['enable'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>',
422                                         esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ),
423                                         esc_attr( $aria_label ),
424                                         ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) )
425                                 );
426                         }
427                 } else {
428                         $url = add_query_arg( array(
429                                 'action' => 'disable',
430                                 'theme'  => $theme_key,
431                                 'paged'  => $page,
432                                 's'      => $s,
433                         ), $url );
434
435                         if ( $this->is_site_themes ) {
436                                 /* translators: %s: theme name */
437                                 $aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) );
438                         } else {
439                                 /* translators: %s: theme name */
440                                 $aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) );
441                         }
442
443                         $actions['disable'] = sprintf( '<a href="%s" aria-label="%s">%s</a>',
444                                 esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ),
445                                 esc_attr( $aria_label ),
446                                 ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) )
447                         );
448                 }
449
450                 if ( current_user_can('edit_themes') ) {
451                         $url = add_query_arg( array(
452                                 'theme' => $theme_key,
453                         ), 'theme-editor.php' );
454
455                         /* translators: %s: theme name */
456                         $aria_label = sprintf( __( 'Open %s in the Theme Editor' ), $theme->display( 'Name' ) );
457
458                         $actions['edit'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>',
459                                 esc_url( $url ),
460                                 esc_attr( $aria_label ),
461                                 __( 'Edit' )
462                         );
463                 }
464
465                 if ( ! $allowed && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && $stylesheet != get_option( 'stylesheet' ) && $stylesheet != get_option( 'template' ) ) {
466                         $url = add_query_arg( array(
467                                 'action'       => 'delete-selected',
468                                 'checked[]'    => $theme_key,
469                                 'theme_status' => $context,
470                                 'paged'        => $page,
471                                 's'            => $s,
472                         ), 'themes.php' );
473
474                         /* translators: %s: theme name */
475                         $aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) );
476
477                         $actions['delete'] = sprintf( '<a href="%s" class="delete" aria-label="%s">%s</a>',
478                                 esc_url( wp_nonce_url( $url, 'bulk-themes' ) ),
479                                 esc_attr( $aria_label ),
480                                 __( 'Delete' )
481                         );
482                 }
483                 /**
484                  * Filters the action links displayed for each theme in the Multisite
485                  * themes list table.
486                  *
487                  * The action links displayed are determined by the theme's status, and
488                  * which Multisite themes list table is being displayed - the Network
489                  * themes list table (themes.php), which displays all installed themes,
490                  * or the Site themes list table (site-themes.php), which displays the
491                  * non-network enabled themes when editing a site in the Network admin.
492                  *
493                  * The default action links for the Network themes list table include
494                  * 'Network Enable', 'Network Disable', 'Edit', and 'Delete'.
495                  *
496                  * The default action links for the Site themes list table include
497                  * 'Enable', 'Disable', and 'Edit'.
498                  *
499                  * @since 2.8.0
500                  *
501                  * @param array    $actions An array of action links.
502                  * @param WP_Theme $theme   The current WP_Theme object.
503                  * @param string   $context Status of the theme.
504                  */
505                 $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context );
506
507                 /**
508                  * Filters the action links of a specific theme in the Multisite themes
509                  * list table.
510                  *
511                  * The dynamic portion of the hook name, `$stylesheet`, refers to the
512                  * directory name of the theme, which in most cases is synonymous
513                  * with the template name.
514                  *
515                  * @since 3.1.0
516                  *
517                  * @param array    $actions An array of action links.
518                  * @param WP_Theme $theme   The current WP_Theme object.
519                  * @param string   $context Status of the theme.
520                  */
521                 $actions = apply_filters( "theme_action_links_$stylesheet", $actions, $theme, $context );
522
523                 echo $this->row_actions( $actions, true );
524         }
525
526         /**
527          * Handles the description column output.
528          *
529          * @since 4.3.0
530          * @access public
531          *
532          * @global string $status
533          * @global array  $totals
534          *
535          * @param WP_Theme $theme The current WP_Theme object.
536          */
537         public function column_description( $theme ) {
538                 global $status, $totals;
539                 if ( $theme->errors() ) {
540                         $pre = $status === 'broken' ? __( 'Broken Theme:' ) . ' ' : '';
541                         echo '<p><strong class="error-message">' . $pre . $theme->errors()->get_error_message() . '</strong></p>';
542                 }
543
544                 if ( $this->is_site_themes ) {
545                         $allowed = $theme->is_allowed( 'site', $this->site_id );
546                 } else {
547                         $allowed = $theme->is_allowed( 'network' );
548                 }
549
550                 $class = ! $allowed ? 'inactive' : 'active';
551                 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) )
552                         $class .= ' update';
553
554                 echo "<div class='theme-description'><p>" . $theme->display( 'Description' ) . "</p></div>
555                         <div class='$class second theme-version-author-uri'>";
556
557                 $stylesheet = $theme->get_stylesheet();
558                 $theme_meta = array();
559
560                 if ( $theme->get('Version') ) {
561                         $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display('Version') );
562                 }
563                 $theme_meta[] = sprintf( __( 'By %s' ), $theme->display('Author') );
564
565                 if ( $theme->get('ThemeURI') ) {
566                         /* translators: %s: theme name */
567                         $aria_label = sprintf( __( 'Visit %s homepage' ), $theme->display( 'Name' ) );
568
569                         $theme_meta[] = sprintf( '<a href="%s" aria-label="%s">%s</a>',
570                                 $theme->display( 'ThemeURI' ),
571                                 esc_attr( $aria_label ),
572                                 __( 'Visit Theme Site' )
573                         );
574                 }
575                 /**
576                  * Filters the array of row meta for each theme in the Multisite themes
577                  * list table.
578                  *
579                  * @since 3.1.0
580                  *
581                  * @param array    $theme_meta An array of the theme's metadata,
582                  *                             including the version, author, and
583                  *                             theme URI.
584                  * @param string   $stylesheet Directory name of the theme.
585                  * @param WP_Theme $theme      WP_Theme object.
586                  * @param string   $status     Status of the theme.
587                  */
588                 $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $stylesheet, $theme, $status );
589                 echo implode( ' | ', $theme_meta );
590
591                 echo '</div>';
592         }
593
594         /**
595          * Handles default column output.
596          *
597          * @since 4.3.0
598          * @access public
599          *
600          * @param WP_Theme $theme       The current WP_Theme object.
601          * @param string   $column_name The current column name.
602          */
603         public function column_default( $theme, $column_name ) {
604                 $stylesheet = $theme->get_stylesheet();
605
606                 /**
607                  * Fires inside each custom column of the Multisite themes list table.
608                  *
609                  * @since 3.1.0
610                  *
611                  * @param string   $column_name Name of the column.
612                  * @param string   $stylesheet  Directory name of the theme.
613                  * @param WP_Theme $theme       Current WP_Theme object.
614                  */
615                 do_action( 'manage_themes_custom_column', $column_name, $stylesheet, $theme );
616         }
617
618         /**
619          * Handles the output for a single table row.
620          *
621          * @since 4.3.0
622          * @access public
623          *
624          * @param WP_Theme $item The current WP_Theme object.
625          */
626         public function single_row_columns( $item ) {
627                 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
628
629                 foreach ( $columns as $column_name => $column_display_name ) {
630                         $extra_classes = '';
631                         if ( in_array( $column_name, $hidden ) ) {
632                                 $extra_classes .= ' hidden';
633                         }
634
635                         switch ( $column_name ) {
636                                 case 'cb':
637                                         echo '<th scope="row" class="check-column">';
638
639                                         $this->column_cb( $item );
640
641                                         echo '</th>';
642                                         break;
643
644                                 case 'name':
645                                         echo "<td class='theme-title column-primary{$extra_classes}'><strong>" . $item->display('Name') . "</strong>";
646
647                                         $this->column_name( $item );
648
649                                         echo "</td>";
650                                         break;
651
652                                 case 'description':
653                                         echo "<td class='column-description desc{$extra_classes}'>";
654
655                                         $this->column_description( $item );
656
657                                         echo '</td>';
658                                         break;
659
660                                 default:
661                                         echo "<td class='$column_name column-$column_name{$extra_classes}'>";
662
663                                         $this->column_default( $item, $column_name );
664
665                                         echo "</td>";
666                                         break;
667                         }
668                 }
669         }
670
671         /**
672          * @global string $status
673          * @global array  $totals
674          *
675          * @param WP_Theme $theme
676          */
677         public function single_row( $theme ) {
678                 global $status, $totals;
679
680                 if ( $this->is_site_themes ) {
681                         $allowed = $theme->is_allowed( 'site', $this->site_id );
682                 } else {
683                         $allowed = $theme->is_allowed( 'network' );
684                 }
685
686                 $stylesheet = $theme->get_stylesheet();
687
688                 $class = ! $allowed ? 'inactive' : 'active';
689                 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
690                         $class .= ' update';
691                 }
692
693                 printf( '<tr class="%s" data-slug="%s">',
694                         esc_attr( $class ),
695                         esc_attr( $stylesheet )
696                 );
697
698                 $this->single_row_columns( $theme );
699
700                 echo "</tr>";
701
702                 if ( $this->is_site_themes )
703                         remove_action( "after_theme_row_$stylesheet", 'wp_theme_update_row' );
704
705                 /**
706                  * Fires after each row in the Multisite themes list table.
707                  *
708                  * @since 3.1.0
709                  *
710                  * @param string   $stylesheet Directory name of the theme.
711                  * @param WP_Theme $theme      Current WP_Theme object.
712                  * @param string   $status     Status of the theme.
713                  */
714                 do_action( 'after_theme_row', $stylesheet, $theme, $status );
715
716                 /**
717                  * Fires after each specific row in the Multisite themes list table.
718                  *
719                  * The dynamic portion of the hook name, `$stylesheet`, refers to the
720                  * directory name of the theme, most often synonymous with the template
721                  * name of the theme.
722                  *
723                  * @since 3.5.0
724                  *
725                  * @param string   $stylesheet Directory name of the theme.
726                  * @param WP_Theme $theme      Current WP_Theme object.
727                  * @param string   $status     Status of the theme.
728                  */
729                 do_action( "after_theme_row_$stylesheet", $stylesheet, $theme, $status );
730         }
731 }