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