GeneratorsAdminList Table

List Table Generator

A WP_List_Table subclass with sortable columns, row and bulk actions, search and the admin page that renders it.

class-acme_briefs_table.php
Saved just now
The table
Columns
4 columns · 3 sortable
Titletitle
SortablePrimary
Authorauthor
SortablePrimary
Statusstatus
SortablePrimary
Createdcreated
SortablePrimary
Actions
2 row · 1 bulk · 3 views
Table extras
Search box
search_box() above the table, wired to the s parameter.
Per-page screen option
add_screen_option plus the set-screen-option filter that lets it save.
<?php
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Plugin Name:       Briefs list table
 * Description:       An admin table of briefs with sorting, search and bulk actions.
 * Version:           1.0.0
 * Requires PHP:      7.4
 */

defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

/**
 * A table of briefs.
 */
class Acme_Briefs_Table extends WP_List_Table {

	/**
	 * Tell the parent what one row is called.
	 */
	public function __construct() {
		parent::__construct(
			array(
				'singular' => 'brief',
				'plural'   => 'briefs',
				'ajax'     => false,
			)
		);
	}

	/**
	 * The columns, in order.
	 *
	 * @return array
	 */
	public function get_columns() {
		return array(
			'cb'      => '<input type="checkbox" />',
			'title'   => __( 'Title', 'acme' ),
			'author'  => __( 'Author', 'acme' ),
			'status'  => __( 'Status', 'acme' ),
			'created' => __( 'Created', 'acme' ),
		);
	}

	/**
	 * Which columns can be sorted, and which is sorted by default.
	 *
	 * @return array
	 */
	public function get_sortable_columns() {
		return array(
			'title'   => array( 'title', true ),
			'status'  => array( 'status', false ),
			'created' => array( 'created', false ),
		);
	}

	/**
	 * Which column carries the row actions.
	 *
	 * @return string
	 */
	protected function get_default_primary_column_name() {
		return 'title';
	}

	/**
	 * The checkbox column.
	 *
	 * @param array $item One row.
	 * @return string
	 */
	public function column_cb( $item ) {
		return sprintf(
			'<input type="checkbox" name="briefs[]" value="%s" />',
			esc_attr( $item['id'] )
		);
	}

	/**
	 * The bulk actions dropdown.
	 *
	 * @return array
	 */
	public function get_bulk_actions() {
		return array(
			'delete' => __( 'Delete permanently', 'acme' ),
		);
	}

	/**
	 * Run a bulk action. Called before anything is fetched.
	 */
	protected function process_bulk_action() {
		$action = $this->current_action();

		if ( ! $action ) {
			return;
		}

		check_admin_referer( 'bulk-' . $this->_args['plural'] );

		if ( ! current_user_can( 'edit_others_posts' ) ) {
			wp_die( esc_html__( 'You are not allowed to do that.', 'acme' ) );
		}

		$ids = isset( $_REQUEST['briefs'] ) ? array_map( 'absint', (array) $_REQUEST['briefs'] ) : array();

		if ( ! $ids ) {
			return;
		}

		switch ( $action ) {
			case 'delete':
				// Delete permanently — act on $ids here.
				break;
		}
	}

	/**
	 * The status links above the table.
	 *
	 * @return array
	 */
	protected function get_views() {
		$current = isset( $_REQUEST['status'] ) ? sanitize_key( $_REQUEST['status'] ) : 'all';
		$base    = remove_query_arg( array( 'status', 'paged' ) );
		$views   = array();

		$views['all'] = sprintf(
			'<a href="%1$s"%2$s>%3$s</a>',
			esc_url( add_query_arg( 'status', 'all', $base ) ),
			'all' === $current ? ' class="current"' : '',
			esc_html( __( 'All', 'acme' ) )
		);

		$views['draft'] = sprintf(
			'<a href="%1$s"%2$s>%3$s</a>',
			esc_url( add_query_arg( 'status', 'draft', $base ) ),
			'draft' === $current ? ' class="current"' : '',
			esc_html( __( 'Drafts', 'acme' ) )
		);

		$views['review'] = sprintf(
			'<a href="%1$s"%2$s>%3$s</a>',
			esc_url( add_query_arg( 'status', 'review', $base ) ),
			'review' === $current ? ' class="current"' : '',
			esc_html( __( 'In review', 'acme' ) )
		);

		return $views;
	}

	/**
	 * The primary column, with its row actions.
	 *
	 * @param array $item One row.
	 * @return string
	 */
	public function column_title( $item ) {
		$actions = array(
			'edit' => sprintf(
				'<a href="%1$s">%2$s</a>',
				esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'edit', 'brief' => $item['id'] ) ), 'acme_edit_' . $item['id'] ) ),
				esc_html( __( 'Edit', 'acme' ) )
			),
			'delete' => sprintf(
				'<a href="%1$s" class="submitdelete">%2$s</a>',
				esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'delete', 'brief' => $item['id'] ) ), 'acme_delete_' . $item['id'] ) ),
				esc_html( __( 'Delete', 'acme' ) )
			),
		);

		return sprintf(
			'<strong>%1$s</strong>%2$s',
			esc_html( $item['title'] ),
			$this->row_actions( $actions )
		);
	}

	/**
	 * @param array $item One row.
	 * @return string
	 */
	public function column_status( $item ) {
		return sprintf(
			'<span class="acme-status acme-status--%1$s">%2$s</span>',
			esc_attr( $item['status'] ),
			esc_html( ucfirst( $item['status'] ) )
		);
	}

	/**
	 * @param array $item One row.
	 * @return string
	 */
	public function column_created( $item ) {
		return esc_html( mysql2date( get_option( 'date_format' ), $item['created'] ) );
	}

	/**
	 * Anything without its own column_ method lands here.
	 *
	 * @param array  $item        One row.
	 * @param string $column_name The column.
	 * @return string
	 */
	public function column_default( $item, $column_name ) {
		return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : '';
	}

	/**
	 * Shown when there is nothing to list.
	 */
	public function no_items() {
		esc_html_e( 'No briefs yet.', 'acme' );
	}

	/**
	 * Fetch, paginate and hand the rows to the parent.
	 *
	 * Order matters: bulk action, then fetch, then pagination, then items.
	 */
	public function prepare_items() {
		$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );

		$this->process_bulk_action();

		$per_page = $this->get_items_per_page( 'acme_briefs_per_page', 20 );
		$paged    = $this->get_pagenum();
		$offset   = ( $paged - 1 ) * $per_page;

		global $wpdb;

		$table   = $wpdb->prefix . 'acme_briefs';
		$orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( $_REQUEST['orderby'] ) : 'title';
		$order   = isset( $_REQUEST['order'] ) && 'desc' === strtolower( $_REQUEST['order'] ) ? 'DESC' : 'ASC';
		$search  = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';

		// Only ever sort by a column you declared sortable.
		$allowed = array_keys( $this->get_sortable_columns() );

		if ( ! in_array( $orderby, $allowed, true ) ) {
			$orderby = 'title';
		}

		$where = 'WHERE 1=1';
		$args  = array();

		if ( '' !== $search ) {
			$where .= ' AND title LIKE %s';
			$args[] = '%' . $wpdb->esc_like( $search ) . '%';
		}

		$args[] = $per_page;
		$args[] = $offset;

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$sql = "SELECT * FROM {$table} {$where} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";

		$items = $wpdb->get_results( $wpdb->prepare( $sql, $args ), ARRAY_A );
		$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} {$where}" );

		$this->set_pagination_args(
			array(
				'total_items' => $total,
				'per_page'    => $per_page,
				'total_pages' => (int) ceil( $total / $per_page ),
			)
		);

		$this->items = $items;
	}
}