]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/includes/class-wp-list-table.php
WordPress 3.9.1-scripts
[autoinstalls/wordpress.git] / wp-admin / includes / class-wp-list-table.php
1 <?php
2 /**
3  * Base class for displaying a list of items in an ajaxified HTML table.
4  *
5  * @package WordPress
6  * @subpackage List_Table
7  * @since 3.1.0
8  * @access private
9  */
10 class WP_List_Table {
11
12         /**
13          * The current list of items
14          *
15          * @since 3.1.0
16          * @var array
17          * @access protected
18          */
19         var $items;
20
21         /**
22          * Various information about the current table
23          *
24          * @since 3.1.0
25          * @var array
26          * @access private
27          */
28         var $_args;
29
30         /**
31          * Various information needed for displaying the pagination
32          *
33          * @since 3.1.0
34          * @var array
35          * @access private
36          */
37         var $_pagination_args = array();
38
39         /**
40          * The current screen
41          *
42          * @since 3.1.0
43          * @var object
44          * @access protected
45          */
46         var $screen;
47
48         /**
49          * Cached bulk actions
50          *
51          * @since 3.1.0
52          * @var array
53          * @access private
54          */
55         var $_actions;
56
57         /**
58          * Cached pagination output
59          *
60          * @since 3.1.0
61          * @var string
62          * @access private
63          */
64         var $_pagination;
65
66         /**
67          * Constructor. The child class should call this constructor from its own constructor
68          *
69          * @param array $args An associative array with information about the current table
70          * @access protected
71          */
72         function __construct( $args = array() ) {
73                 $args = wp_parse_args( $args, array(
74                         'plural' => '',
75                         'singular' => '',
76                         'ajax' => false,
77                         'screen' => null,
78                 ) );
79
80                 $this->screen = convert_to_screen( $args['screen'] );
81
82                 add_filter( "manage_{$this->screen->id}_columns", array( $this, 'get_columns' ), 0 );
83
84                 if ( !$args['plural'] )
85                         $args['plural'] = $this->screen->base;
86
87                 $args['plural'] = sanitize_key( $args['plural'] );
88                 $args['singular'] = sanitize_key( $args['singular'] );
89
90                 $this->_args = $args;
91
92                 if ( $args['ajax'] ) {
93                         // wp_enqueue_script( 'list-table' );
94                         add_action( 'admin_footer', array( $this, '_js_vars' ) );
95                 }
96         }
97
98         /**
99          * Checks the current user's permissions
100          * @uses wp_die()
101          *
102          * @since 3.1.0
103          * @access public
104          * @abstract
105          */
106         function ajax_user_can() {
107                 die( 'function WP_List_Table::ajax_user_can() must be over-ridden in a sub-class.' );
108         }
109
110         /**
111          * Prepares the list of items for displaying.
112          * @uses WP_List_Table::set_pagination_args()
113          *
114          * @since 3.1.0
115          * @access public
116          * @abstract
117          */
118         function prepare_items() {
119                 die( 'function WP_List_Table::prepare_items() must be over-ridden in a sub-class.' );
120         }
121
122         /**
123          * An internal method that sets all the necessary pagination arguments
124          *
125          * @param array $args An associative array with information about the pagination
126          * @access protected
127          */
128         function set_pagination_args( $args ) {
129                 $args = wp_parse_args( $args, array(
130                         'total_items' => 0,
131                         'total_pages' => 0,
132                         'per_page' => 0,
133                 ) );
134
135                 if ( !$args['total_pages'] && $args['per_page'] > 0 )
136                         $args['total_pages'] = ceil( $args['total_items'] / $args['per_page'] );
137
138                 // redirect if page number is invalid and headers are not already sent
139                 if ( ! headers_sent() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && $args['total_pages'] > 0 && $this->get_pagenum() > $args['total_pages'] ) {
140                         wp_redirect( add_query_arg( 'paged', $args['total_pages'] ) );
141                         exit;
142                 }
143
144                 $this->_pagination_args = $args;
145         }
146
147         /**
148          * Access the pagination args
149          *
150          * @since 3.1.0
151          * @access public
152          *
153          * @param string $key
154          * @return array
155          */
156         function get_pagination_arg( $key ) {
157                 if ( 'page' == $key )
158                         return $this->get_pagenum();
159
160                 if ( isset( $this->_pagination_args[$key] ) )
161                         return $this->_pagination_args[$key];
162         }
163
164         /**
165          * Whether the table has items to display or not
166          *
167          * @since 3.1.0
168          * @access public
169          *
170          * @return bool
171          */
172         function has_items() {
173                 return !empty( $this->items );
174         }
175
176         /**
177          * Message to be displayed when there are no items
178          *
179          * @since 3.1.0
180          * @access public
181          */
182         function no_items() {
183                 _e( 'No items found.' );
184         }
185
186         /**
187          * Display the search box.
188          *
189          * @since 3.1.0
190          * @access public
191          *
192          * @param string $text The search button text
193          * @param string $input_id The search input id
194          */
195         function search_box( $text, $input_id ) {
196                 if ( empty( $_REQUEST['s'] ) && !$this->has_items() )
197                         return;
198
199                 $input_id = $input_id . '-search-input';
200
201                 if ( ! empty( $_REQUEST['orderby'] ) )
202                         echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
203                 if ( ! empty( $_REQUEST['order'] ) )
204                         echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
205                 if ( ! empty( $_REQUEST['post_mime_type'] ) )
206                         echo '<input type="hidden" name="post_mime_type" value="' . esc_attr( $_REQUEST['post_mime_type'] ) . '" />';
207                 if ( ! empty( $_REQUEST['detached'] ) )
208                         echo '<input type="hidden" name="detached" value="' . esc_attr( $_REQUEST['detached'] ) . '" />';
209 ?>
210 <p class="search-box">
211         <label class="screen-reader-text" for="<?php echo $input_id ?>"><?php echo $text; ?>:</label>
212         <input type="search" id="<?php echo $input_id ?>" name="s" value="<?php _admin_search_query(); ?>" />
213         <?php submit_button( $text, 'button', false, false, array('id' => 'search-submit') ); ?>
214 </p>
215 <?php
216         }
217
218         /**
219          * Get an associative array ( id => link ) with the list
220          * of views available on this table.
221          *
222          * @since 3.1.0
223          * @access protected
224          *
225          * @return array
226          */
227         function get_views() {
228                 return array();
229         }
230
231         /**
232          * Display the list of views available on this table.
233          *
234          * @since 3.1.0
235          * @access public
236          */
237         function views() {
238                 $views = $this->get_views();
239                 /**
240                  * Filter the list of available list table views.
241                  *
242                  * The dynamic portion of the hook name, $this->screen->id, refers
243                  * to the ID of the current screen, usually a string.
244                  *
245                  * @since 3.5.0
246                  *
247                  * @param array $views An array of available list table views.
248                  */
249                 $views = apply_filters( "views_{$this->screen->id}", $views );
250
251                 if ( empty( $views ) )
252                         return;
253
254                 echo "<ul class='subsubsub'>\n";
255                 foreach ( $views as $class => $view ) {
256                         $views[ $class ] = "\t<li class='$class'>$view";
257                 }
258                 echo implode( " |</li>\n", $views ) . "</li>\n";
259                 echo "</ul>";
260         }
261
262         /**
263          * Get an associative array ( option_name => option_title ) with the list
264          * of bulk actions available on this table.
265          *
266          * @since 3.1.0
267          * @access protected
268          *
269          * @return array
270          */
271         function get_bulk_actions() {
272                 return array();
273         }
274
275         /**
276          * Display the bulk actions dropdown.
277          *
278          * @since 3.1.0
279          * @access public
280          */
281         function bulk_actions() {
282                 if ( is_null( $this->_actions ) ) {
283                         $no_new_actions = $this->_actions = $this->get_bulk_actions();
284                         /**
285                          * Filter the list table Bulk Actions drop-down.
286                          *
287                          * The dynamic portion of the hook name, $this->screen->id, refers
288                          * to the ID of the current screen, usually a string.
289                          *
290                          * This filter can currently only be used to remove bulk actions.
291                          *
292                          * @since 3.5.0
293                          *
294                          * @param array $actions An array of the available bulk actions.
295                          */
296                         $this->_actions = apply_filters( "bulk_actions-{$this->screen->id}", $this->_actions );
297                         $this->_actions = array_intersect_assoc( $this->_actions, $no_new_actions );
298                         $two = '';
299                 } else {
300                         $two = '2';
301                 }
302
303                 if ( empty( $this->_actions ) )
304                         return;
305
306                 echo "<select name='action$two'>\n";
307                 echo "<option value='-1' selected='selected'>" . __( 'Bulk Actions' ) . "</option>\n";
308
309                 foreach ( $this->_actions as $name => $title ) {
310                         $class = 'edit' == $name ? ' class="hide-if-no-js"' : '';
311
312                         echo "\t<option value='$name'$class>$title</option>\n";
313                 }
314
315                 echo "</select>\n";
316
317                 submit_button( __( 'Apply' ), 'action', false, false, array( 'id' => "doaction$two" ) );
318                 echo "\n";
319         }
320
321         /**
322          * Get the current action selected from the bulk actions dropdown.
323          *
324          * @since 3.1.0
325          * @access public
326          *
327          * @return string|bool The action name or False if no action was selected
328          */
329         function current_action() {
330                 if ( isset( $_REQUEST['action'] ) && -1 != $_REQUEST['action'] )
331                         return $_REQUEST['action'];
332
333                 if ( isset( $_REQUEST['action2'] ) && -1 != $_REQUEST['action2'] )
334                         return $_REQUEST['action2'];
335
336                 return false;
337         }
338
339         /**
340          * Generate row actions div
341          *
342          * @since 3.1.0
343          * @access protected
344          *
345          * @param array $actions The list of actions
346          * @param bool $always_visible Whether the actions should be always visible
347          * @return string
348          */
349         function row_actions( $actions, $always_visible = false ) {
350                 $action_count = count( $actions );
351                 $i = 0;
352
353                 if ( !$action_count )
354                         return '';
355
356                 $out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
357                 foreach ( $actions as $action => $link ) {
358                         ++$i;
359                         ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
360                         $out .= "<span class='$action'>$link$sep</span>";
361                 }
362                 $out .= '</div>';
363
364                 return $out;
365         }
366
367         /**
368          * Display a monthly dropdown for filtering items
369          *
370          * @since 3.1.0
371          * @access protected
372          */
373         function months_dropdown( $post_type ) {
374                 global $wpdb, $wp_locale;
375
376                 $months = $wpdb->get_results( $wpdb->prepare( "
377                         SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
378                         FROM $wpdb->posts
379                         WHERE post_type = %s
380                         ORDER BY post_date DESC
381                 ", $post_type ) );
382
383                 /**
384                  * Filter the 'Months' drop-down results.
385                  *
386                  * @since 3.7.0
387                  *
388                  * @param object $months    The months drop-down query results.
389                  * @param string $post_type The post type.
390                  */
391                 $months = apply_filters( 'months_dropdown_results', $months, $post_type );
392
393                 $month_count = count( $months );
394
395                 if ( !$month_count || ( 1 == $month_count && 0 == $months[0]->month ) )
396                         return;
397
398                 $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
399 ?>
400                 <select name='m'>
401                         <option<?php selected( $m, 0 ); ?> value='0'><?php _e( 'All dates' ); ?></option>
402 <?php
403                 foreach ( $months as $arc_row ) {
404                         if ( 0 == $arc_row->year )
405                                 continue;
406
407                         $month = zeroise( $arc_row->month, 2 );
408                         $year = $arc_row->year;
409
410                         printf( "<option %s value='%s'>%s</option>\n",
411                                 selected( $m, $year . $month, false ),
412                                 esc_attr( $arc_row->year . $month ),
413                                 /* translators: 1: month name, 2: 4-digit year */
414                                 sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
415                         );
416                 }
417 ?>
418                 </select>
419 <?php
420         }
421
422         /**
423          * Display a view switcher
424          *
425          * @since 3.1.0
426          * @access protected
427          */
428         function view_switcher( $current_mode ) {
429                 $modes = array(
430                         'list'    => __( 'List View' ),
431                         'excerpt' => __( 'Excerpt View' )
432                 );
433
434 ?>
435                 <input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
436                 <div class="view-switch">
437 <?php
438                         foreach ( $modes as $mode => $title ) {
439                                 $class = ( $current_mode == $mode ) ? 'class="current"' : '';
440                                 echo "<a href='" . esc_url( add_query_arg( 'mode', $mode, $_SERVER['REQUEST_URI'] ) ) . "' $class><img id='view-switch-$mode' src='" . esc_url( includes_url( 'images/blank.gif' ) ) . "' width='20' height='20' title='$title' alt='$title' /></a>\n";
441                         }
442                 ?>
443                 </div>
444 <?php
445         }
446
447         /**
448          * Display a comment count bubble
449          *
450          * @since 3.1.0
451          * @access protected
452          *
453          * @param int $post_id
454          * @param int $pending_comments
455          */
456         function comments_bubble( $post_id, $pending_comments ) {
457                 $pending_phrase = sprintf( __( '%s pending' ), number_format( $pending_comments ) );
458
459                 if ( $pending_comments )
460                         echo '<strong>';
461
462                 echo "<a href='" . esc_url( add_query_arg( 'p', $post_id, admin_url( 'edit-comments.php' ) ) ) . "' title='" . esc_attr( $pending_phrase ) . "' class='post-com-count'><span class='comment-count'>" . number_format_i18n( get_comments_number() ) . "</span></a>";
463
464                 if ( $pending_comments )
465                         echo '</strong>';
466         }
467
468         /**
469          * Get the current page number
470          *
471          * @since 3.1.0
472          * @access protected
473          *
474          * @return int
475          */
476         function get_pagenum() {
477                 $pagenum = isset( $_REQUEST['paged'] ) ? absint( $_REQUEST['paged'] ) : 0;
478
479                 if( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] )
480                         $pagenum = $this->_pagination_args['total_pages'];
481
482                 return max( 1, $pagenum );
483         }
484
485         /**
486          * Get number of items to display on a single page
487          *
488          * @since 3.1.0
489          * @access protected
490          *
491          * @return int
492          */
493         function get_items_per_page( $option, $default = 20 ) {
494                 $per_page = (int) get_user_option( $option );
495                 if ( empty( $per_page ) || $per_page < 1 )
496                         $per_page = $default;
497
498                 /**
499                  * Filter the number of items to be displayed on each page of the list table.
500                  *
501                  * The dynamic hook name, $option, refers to the per page option depending
502                  * on the type of list table in use. Possible values may include:
503                  * 'edit_comments_per_page', 'sites_network_per_page', 'site_themes_network_per_page',
504                  * 'themes_netework_per_page', 'users_network_per_page', 'edit_{$post_type}', etc.
505                  *
506                  * @since 2.9.0
507                  *
508                  * @param int $per_page Number of items to be displayed. Default 20.
509                  */
510                 return (int) apply_filters( $option, $per_page );
511         }
512
513         /**
514          * Display the pagination.
515          *
516          * @since 3.1.0
517          * @access protected
518          */
519         function pagination( $which ) {
520                 if ( empty( $this->_pagination_args ) )
521                         return;
522
523                 extract( $this->_pagination_args, EXTR_SKIP );
524
525                 $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
526
527                 $current = $this->get_pagenum();
528
529                 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
530
531                 $current_url = remove_query_arg( array( 'hotkeys_highlight_last', 'hotkeys_highlight_first' ), $current_url );
532
533                 $page_links = array();
534
535                 $disable_first = $disable_last = '';
536                 if ( $current == 1 )
537                         $disable_first = ' disabled';
538                 if ( $current == $total_pages )
539                         $disable_last = ' disabled';
540
541                 $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
542                         'first-page' . $disable_first,
543                         esc_attr__( 'Go to the first page' ),
544                         esc_url( remove_query_arg( 'paged', $current_url ) ),
545                         '&laquo;'
546                 );
547
548                 $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
549                         'prev-page' . $disable_first,
550                         esc_attr__( 'Go to the previous page' ),
551                         esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
552                         '&lsaquo;'
553                 );
554
555                 if ( 'bottom' == $which )
556                         $html_current_page = $current;
557                 else
558                         $html_current_page = sprintf( "<input class='current-page' title='%s' type='text' name='paged' value='%s' size='%d' />",
559                                 esc_attr__( 'Current page' ),
560                                 $current,
561                                 strlen( $total_pages )
562                         );
563
564                 $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
565                 $page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . '</span>';
566
567                 $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
568                         'next-page' . $disable_last,
569                         esc_attr__( 'Go to the next page' ),
570                         esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
571                         '&rsaquo;'
572                 );
573
574                 $page_links[] = sprintf( "<a class='%s' title='%s' href='%s'>%s</a>",
575                         'last-page' . $disable_last,
576                         esc_attr__( 'Go to the last page' ),
577                         esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
578                         '&raquo;'
579                 );
580
581                 $pagination_links_class = 'pagination-links';
582                 if ( ! empty( $infinite_scroll ) )
583                         $pagination_links_class = ' hide-if-js';
584                 $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
585
586                 if ( $total_pages )
587                         $page_class = $total_pages < 2 ? ' one-page' : '';
588                 else
589                         $page_class = ' no-pages';
590
591                 $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
592
593                 echo $this->_pagination;
594         }
595
596         /**
597          * Get a list of columns. The format is:
598          * 'internal-name' => 'Title'
599          *
600          * @since 3.1.0
601          * @access protected
602          * @abstract
603          *
604          * @return array
605          */
606         function get_columns() {
607                 die( 'function WP_List_Table::get_columns() must be over-ridden in a sub-class.' );
608         }
609
610         /**
611          * Get a list of sortable columns. The format is:
612          * 'internal-name' => 'orderby'
613          * or
614          * 'internal-name' => array( 'orderby', true )
615          *
616          * The second format will make the initial sorting order be descending
617          *
618          * @since 3.1.0
619          * @access protected
620          *
621          * @return array
622          */
623         function get_sortable_columns() {
624                 return array();
625         }
626
627         /**
628          * Get a list of all, hidden and sortable columns, with filter applied
629          *
630          * @since 3.1.0
631          * @access protected
632          *
633          * @return array
634          */
635         function get_column_info() {
636                 if ( isset( $this->_column_headers ) )
637                         return $this->_column_headers;
638
639                 $columns = get_column_headers( $this->screen );
640                 $hidden = get_hidden_columns( $this->screen );
641
642                 $sortable_columns = $this->get_sortable_columns();
643                 /**
644                  * Filter the list table sortable columns for a specific screen.
645                  *
646                  * The dynamic portion of the hook name, $this->screen->id, refers
647                  * to the ID of the current screen, usually a string.
648                  *
649                  * @since 3.5.0
650                  *
651                  * @param array $sortable_columns An array of sortable columns.
652                  */
653                 $_sortable = apply_filters( "manage_{$this->screen->id}_sortable_columns", $sortable_columns );
654
655                 $sortable = array();
656                 foreach ( $_sortable as $id => $data ) {
657                         if ( empty( $data ) )
658                                 continue;
659
660                         $data = (array) $data;
661                         if ( !isset( $data[1] ) )
662                                 $data[1] = false;
663
664                         $sortable[$id] = $data;
665                 }
666
667                 $this->_column_headers = array( $columns, $hidden, $sortable );
668
669                 return $this->_column_headers;
670         }
671
672         /**
673          * Return number of visible columns
674          *
675          * @since 3.1.0
676          * @access public
677          *
678          * @return int
679          */
680         function get_column_count() {
681                 list ( $columns, $hidden ) = $this->get_column_info();
682                 $hidden = array_intersect( array_keys( $columns ), array_filter( $hidden ) );
683                 return count( $columns ) - count( $hidden );
684         }
685
686         /**
687          * Print column headers, accounting for hidden and sortable columns.
688          *
689          * @since 3.1.0
690          * @access protected
691          *
692          * @param bool $with_id Whether to set the id attribute or not
693          */
694         function print_column_headers( $with_id = true ) {
695                 list( $columns, $hidden, $sortable ) = $this->get_column_info();
696
697                 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
698                 $current_url = remove_query_arg( 'paged', $current_url );
699
700                 if ( isset( $_GET['orderby'] ) )
701                         $current_orderby = $_GET['orderby'];
702                 else
703                         $current_orderby = '';
704
705                 if ( isset( $_GET['order'] ) && 'desc' == $_GET['order'] )
706                         $current_order = 'desc';
707                 else
708                         $current_order = 'asc';
709
710                 if ( ! empty( $columns['cb'] ) ) {
711                         static $cb_counter = 1;
712                         $columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>'
713                                 . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
714                         $cb_counter++;
715                 }
716
717                 foreach ( $columns as $column_key => $column_display_name ) {
718                         $class = array( 'manage-column', "column-$column_key" );
719
720                         $style = '';
721                         if ( in_array( $column_key, $hidden ) )
722                                 $style = 'display:none;';
723
724                         $style = ' style="' . $style . '"';
725
726                         if ( 'cb' == $column_key )
727                                 $class[] = 'check-column';
728                         elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) )
729                                 $class[] = 'num';
730
731                         if ( isset( $sortable[$column_key] ) ) {
732                                 list( $orderby, $desc_first ) = $sortable[$column_key];
733
734                                 if ( $current_orderby == $orderby ) {
735                                         $order = 'asc' == $current_order ? 'desc' : 'asc';
736                                         $class[] = 'sorted';
737                                         $class[] = $current_order;
738                                 } else {
739                                         $order = $desc_first ? 'desc' : 'asc';
740                                         $class[] = 'sortable';
741                                         $class[] = $desc_first ? 'asc' : 'desc';
742                                 }
743
744                                 $column_display_name = '<a href="' . esc_url( add_query_arg( compact( 'orderby', 'order' ), $current_url ) ) . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
745                         }
746
747                         $id = $with_id ? "id='$column_key'" : '';
748
749                         if ( !empty( $class ) )
750                                 $class = "class='" . join( ' ', $class ) . "'";
751
752                         echo "<th scope='col' $id $class $style>$column_display_name</th>";
753                 }
754         }
755
756         /**
757          * Display the table
758          *
759          * @since 3.1.0
760          * @access public
761          */
762         function display() {
763                 extract( $this->_args );
764
765                 $this->display_tablenav( 'top' );
766
767 ?>
768 <table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
769         <thead>
770         <tr>
771                 <?php $this->print_column_headers(); ?>
772         </tr>
773         </thead>
774
775         <tfoot>
776         <tr>
777                 <?php $this->print_column_headers( false ); ?>
778         </tr>
779         </tfoot>
780
781         <tbody id="the-list"<?php if ( $singular ) echo " data-wp-lists='list:$singular'"; ?>>
782                 <?php $this->display_rows_or_placeholder(); ?>
783         </tbody>
784 </table>
785 <?php
786                 $this->display_tablenav( 'bottom' );
787         }
788
789         /**
790          * Get a list of CSS classes for the <table> tag
791          *
792          * @since 3.1.0
793          * @access protected
794          *
795          * @return array
796          */
797         function get_table_classes() {
798                 return array( 'widefat', 'fixed', $this->_args['plural'] );
799         }
800
801         /**
802          * Generate the table navigation above or below the table
803          *
804          * @since 3.1.0
805          * @access protected
806          */
807         function display_tablenav( $which ) {
808                 if ( 'top' == $which )
809                         wp_nonce_field( 'bulk-' . $this->_args['plural'] );
810 ?>
811         <div class="tablenav <?php echo esc_attr( $which ); ?>">
812
813                 <div class="alignleft actions bulkactions">
814                         <?php $this->bulk_actions(); ?>
815                 </div>
816 <?php
817                 $this->extra_tablenav( $which );
818                 $this->pagination( $which );
819 ?>
820
821                 <br class="clear" />
822         </div>
823 <?php
824         }
825
826         /**
827          * Extra controls to be displayed between bulk actions and pagination
828          *
829          * @since 3.1.0
830          * @access protected
831          */
832         function extra_tablenav( $which ) {}
833
834         /**
835          * Generate the <tbody> part of the table
836          *
837          * @since 3.1.0
838          * @access protected
839          */
840         function display_rows_or_placeholder() {
841                 if ( $this->has_items() ) {
842                         $this->display_rows();
843                 } else {
844                         list( $columns, $hidden ) = $this->get_column_info();
845                         echo '<tr class="no-items"><td class="colspanchange" colspan="' . $this->get_column_count() . '">';
846                         $this->no_items();
847                         echo '</td></tr>';
848                 }
849         }
850
851         /**
852          * Generate the table rows
853          *
854          * @since 3.1.0
855          * @access protected
856          */
857         function display_rows() {
858                 foreach ( $this->items as $item )
859                         $this->single_row( $item );
860         }
861
862         /**
863          * Generates content for a single row of the table
864          *
865          * @since 3.1.0
866          * @access protected
867          *
868          * @param object $item The current item
869          */
870         function single_row( $item ) {
871                 static $row_class = '';
872                 $row_class = ( $row_class == '' ? ' class="alternate"' : '' );
873
874                 echo '<tr' . $row_class . '>';
875                 $this->single_row_columns( $item );
876                 echo '</tr>';
877         }
878
879         /**
880          * Generates the columns for a single row of the table
881          *
882          * @since 3.1.0
883          * @access protected
884          *
885          * @param object $item The current item
886          */
887         function single_row_columns( $item ) {
888                 list( $columns, $hidden ) = $this->get_column_info();
889
890                 foreach ( $columns as $column_name => $column_display_name ) {
891                         $class = "class='$column_name column-$column_name'";
892
893                         $style = '';
894                         if ( in_array( $column_name, $hidden ) )
895                                 $style = ' style="display:none;"';
896
897                         $attributes = "$class$style";
898
899                         if ( 'cb' == $column_name ) {
900                                 echo '<th scope="row" class="check-column">';
901                                 echo $this->column_cb( $item );
902                                 echo '</th>';
903                         }
904                         elseif ( method_exists( $this, 'column_' . $column_name ) ) {
905                                 echo "<td $attributes>";
906                                 echo call_user_func( array( $this, 'column_' . $column_name ), $item );
907                                 echo "</td>";
908                         }
909                         else {
910                                 echo "<td $attributes>";
911                                 echo $this->column_default( $item, $column_name );
912                                 echo "</td>";
913                         }
914                 }
915         }
916
917         /**
918          * Handle an incoming ajax request (called from admin-ajax.php)
919          *
920          * @since 3.1.0
921          * @access public
922          */
923         function ajax_response() {
924                 $this->prepare_items();
925
926                 extract( $this->_args );
927                 extract( $this->_pagination_args, EXTR_SKIP );
928
929                 ob_start();
930                 if ( ! empty( $_REQUEST['no_placeholder'] ) )
931                         $this->display_rows();
932                 else
933                         $this->display_rows_or_placeholder();
934
935                 $rows = ob_get_clean();
936
937                 $response = array( 'rows' => $rows );
938
939                 if ( isset( $total_items ) )
940                         $response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
941
942                 if ( isset( $total_pages ) ) {
943                         $response['total_pages'] = $total_pages;
944                         $response['total_pages_i18n'] = number_format_i18n( $total_pages );
945                 }
946
947                 die( json_encode( $response ) );
948         }
949
950         /**
951          * Send required variables to JavaScript land
952          *
953          * @access private
954          */
955         function _js_vars() {
956                 $args = array(
957                         'class'  => get_class( $this ),
958                         'screen' => array(
959                                 'id'   => $this->screen->id,
960                                 'base' => $this->screen->base,
961                         )
962                 );
963
964                 printf( "<script type='text/javascript'>list_args = %s;</script>\n", json_encode( $args ) );
965         }
966 }