includes/abstracts/abstract.llms.admin.table.php
<?php
/**
* Admin Table Abstract
*
* @package LifterLMS/Abstracts/Classes
*
* @since 3.2.0
* @version 7.3.0
*/
defined( 'ABSPATH' ) || exit;
/**
* LLMS_Admin_Table abstract class.
*
* @since 3.2.0
* @since 3.34.0 Added get_table_classes().
* @since 3.37.7 Fix PHP 7.4 deprecation notice.
*/
abstract class LLMS_Admin_Table extends LLMS_Abstract_Exportable_Admin_Table {
/**
* Unique ID for the Table.
*
* @var string
*/
protected $id = '';
/**
* When pagination is enabled, the current page.
*
* @var integer
*/
protected $current_page = 1;
/**
* Value of the field being filtered by.
*
* @var string Only applicable if $filterby is set.
*/
protected $filter = '';
/**
* Field results are filtered by.
*
* @var string
*/
protected $filterby = '';
/**
* Is the Table Exportable?
*
* @var bool
*/
protected $is_exportable = false;
/**
* When pagination enabled, determines if this is the last page of results.
*
* @var bool
*/
protected $is_last_page = true;
/**
* If true, tfoot will add ajax pagination links.
*
* @var bool
*/
protected $is_paginated = false;
/**
* Determine if the table is filterable.
*
* @var bool
*/
protected $is_filterable = false;
/**
* If true will be a table with a larger font size.
*
* @var bool
*/
protected $is_large = false;
/**
* Determine of the table is searchable.
*
* @var bool
*/
protected $is_searchable = false;
/**
* If true, tbody will be zebra striped.
*
* @var bool
*/
protected $is_zebra = true;
/**
* If an integer supplied, used to jump to last page.
*
* @var int
*/
protected $max_pages = null;
/**
* Results sort order.
*
* @var string 'ASC' or 'DESC'.
* Only applicable of $orderby is not set.
*/
protected $order = '';
/**
* Field results are sorted by.
*
* @var string
*/
protected $orderby = '';
/**
* Number of records to display per page.
*
* @var int
*/
protected $per_page = -1;
/**
* The search query submitted for a searchable table.
*
* @var string
*/
protected $search = '';
/**
* Table Data.
*
* @var array Array of objects or arrays.
* Each item represents as row in the table's body, each item is a cell.
*/
protected $tbody_data = array();
/**
* Table Title Displayed on Screen.
*
* @var string
*/
protected $title = '';
/**
* Retrieve data for a cell.
*
* @since 3.2.0
*
* @param string $key The column ID/key.
* @param mixed $data Object/array of data that the function can use to extract the data.
* @return mixed
*/
abstract protected function get_data( $key, $data );
/**
* Execute a query to retrieve results from the table.
*
* @since 3.2.0
*
* @param array $args Array of query args.
* @return mixed
*/
abstract public function get_results( $args = array() );
/**
* Define the structure of arguments used to pass to the get_results method.
*
* @since 2.3.0
*
* @return array
*/
abstract public function set_args();
/**
* Define the structure of the table.
*
* @since 3.2.0
*
* @return array
*/
abstract protected function set_columns();
/**
* Constructor.
*
* @since 3.2.0
*
* @return void
*/
public function __construct() {
$this->title = $this->set_title();
$this->register_hooks();
}
/**
* Ensure that a valid array of data is passed to a query.
*
* Used by AJAX methods to clean unnecessary parameters before passing the request data
* to the get_results function.
*
* @since 3.2.0
*
* @param array $args Array of arguments
* @return array
*/
protected function clean_args( $args = array() ) {
$allowed = array_keys( $this->get_args() );
foreach ( $args as $key => $val ) {
if ( ! in_array( $key, $allowed ) ) {
unset( $args[ $key ] );
}
}
return $args;
}
/**
* Ensures that all data requested by $this->get_data is filterable
* before being output on screen / in the export file.
*
* @since 3.2.0
* @since 3.17.6 Unknown.
*
* @param mixed $value Value to be displayed.
* @param string $key Column key/ID.
* @param mixed $data Original data object/array.
* @param string $context Display context [display|export].
* @return mixed
*/
protected function filter_get_data( $value, $key, $data, $context = 'display' ) {
/**
* Filters the table data.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.2.0
*
* @param mixed $value Value to be displayed.
* @param string $key Column key/ID.
* @param mixed $data Original data object/array.
* @param string $context Display context [display|export].
* @param LLMS_Admin_Table $table_object Instance of the class extending `LLMS_Admin_Table`.
*/
return apply_filters( "llms_table_get_data_{$this->id}", $value, $key, $data, $context, $this );
}
/**
* Retrieve the arguments defined in `set_args`.
*
* @since 3.2.0
* @since 3.15.0 Fix filter name.
*
* @return array
*/
public function get_args() {
$default = array(
'page' => $this->get_current_page(),
'order' => $this->get_order(),
'orderby' => $this->get_orderby(),
);
if ( $this->is_filterable ) {
$default['filter'] = $this->get_filter();
$default['filterby'] = $this->get_filterby();
}
if ( $this->is_searchable ) {
$default['search'] = $this->get_search();
}
$args = wp_parse_args( $this->set_args(), $default );
/**
* Filters the arguments used to build the query.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param array $args Arguments to build the query whose results will populate the table.
*/
return apply_filters( "llms_table_get_args_{$this->id}", $args );
}
/**
* Retrieve the array of columns defined by set_columns.
* @since 3.2.0
* @since 3.24.0 Unknown.
*
* @param string $context Display context [display|export].
* @return array
*/
public function get_columns( $context = 'display' ) {
/**
* Filters the array of table columns.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.2.0
*
* @param array $columns The array of table columns.
* @param string $context Display context [display|export].
*/
$cols = apply_filters( "llms_table_get_{$this->id}_columns", $this->set_columns(), $context );
if ( $this->is_exportable ) {
foreach ( $cols as $id => $data ) {
if ( ! $this->is_col_visible( $data, $context ) ) {
unset( $cols[ $id ] );
}
}
}
return $cols;
}
/**
* Get the current page.
*
* @since 3.2.0
*
* @return int
*/
public function get_current_page() {
return $this->current_page;
}
/**
* Get `$this->empty_msg` string.
*
* @since 3.2.0
* @since 3.15.0 Fix filter name.
*
* @return string
*/
public function get_empty_message() {
/**
* Filters the message displayed when the table is empty.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param string $columns The message displayed when the table is empty.
*/
return apply_filters( "llms_table_get_{$this->id}_empty_message", $this->set_empty_message() );
}
/**
* Get the text for the default/placeholder for a filterable column.
*
* @since 3.4.0
* @since 3.15.0 Fix filter name.
* @since 7.3.0 Fixed typo in function name (`is_strinp` => `is_string` ).
*
* @param string $column_id The ID of the column.
* @return string
*/
public function get_filter_placeholder( $column_id, $column_data ) {
$placeholder = __( 'Any', 'lifterlms' );
if ( is_array( $column_data ) && isset( $column_data['title'] ) ) {
$placeholder = sprintf( __( 'Any %s', 'lifterlms' ), $column_data['title'] );
} elseif ( is_string( $column_data ) ) {
$placeholder = sprintf( __( 'Any %s', 'lifterlms' ), $column_data );
}
/**
* Filters the placeholder string for a filterable column.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param string $placeholder Placeholder string.
* @param string $column_id The ID of the column.
*/
return apply_filters( "llms_table_get_{$this->id}_filter_placeholder", $placeholder, $column_id );
}
/**
* Get the current filter.
*
* @since 3.4.0
*
* @return string
*/
public function get_filter() {
return $this->filter;
}
/**
* Get the current field results are filtered by.
*
* @since 3.4.0
*
* @return string
*/
public function get_filterby() {
return $this->filterby;
}
/**
* Retrieve a modified classname that can be passed via AJAX for new queries.
*
* @since 3.2.0
*
* @return string
*/
public function get_handler() {
return str_replace( 'LLMS_Table_', '', get_class( $this ) );
}
/**
* Retrieve the max number of pages for the table.
*
* @since 3.15.0
*
* @return int
*/
public function get_max_pages() {
return $this->max_pages;
}
/**
* Get the current sort order.
*
* @since 3.2.0
*
* @return string
*/
public function get_order() {
return $this->order;
}
/**
* Get the current field results are ordered by.
*
* @since 3.2.0
*
* @return string
*/
public function get_orderby() {
return $this->orderby;
}
/**
* Get the current number of results to display per page.
*
* @since 3.28.0
*
* @return int
*/
public function get_per_page() {
return $this->per_page;
}
/**
* Gets the opposite of the current order.
*
* Used to determine what order should be displayed when resorting.
*
* @since 3.2.0
*
* @return string
*/
protected function get_new_order( $orderby = '' ) {
// Current order matches submitted order, return opposite.
if ( $this->orderby === $orderby ) {
return ( 'ASC' === $this->order ) ? 'DESC' : 'ASC';
} else {
return 'ASC';
}
}
/**
* Retrieves the current search query.
*
* @since 3.2.0
*
* @return string
*/
public function get_search() {
return esc_attr( trim( $this->search ) );
}
/**
* Returns an array of CSS class names to use on this table.
*
* @since 3.34.0
*
* @return array
*/
protected function get_table_classes() {
$classes = array(
'llms-table',
'llms-gb-table',
'llms-gb-table-' . $this->id,
);
if ( $this->is_zebra ) {
$classes[] = 'zebra';
}
if ( $this->is_large ) {
$classes[] = 'size-large';
}
/**
* Filters the CSS classes to use on the table.
*
* @since 3.34.0
*
* @param array $classes CSS class names.
* @param array $table_id Id property of this table object.
*/
return apply_filters( 'llms_table_get_table_classes', $classes, $this->id );
}
/**
* Get HTML for the filters displayed in the head of the table.
*
* @since 3.4.0
*
* @return string
*/
public function get_table_filters_html() {
ob_start();
?>
<div class="llms-table-filters">
<?php foreach ( $this->get_columns() as $id => $data ) : ?>
<?php if ( is_array( $data ) && isset( $data['filterable'] ) && is_array( $data['filterable'] ) ) : ?>
<div class="llms-table-filter-wrap">
<select class="llms-select2 llms-table-filter" id="<?php printf( '%1$s-%2$s-filter', $this->id, $id ); ?>" name="<?php echo $id; ?>">
<option value="<?php echo $this->get_filter(); ?>"><?php echo $this->get_filter_placeholder( $id, $data ); ?></option>
<?php foreach ( $data['filterable'] as $val => $name ) : ?>
<option value="<?php echo $val; ?>"><?php echo $name; ?></option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Get the HTML for the entire table.
*
* @since 3.2.0
* @since 3.17.8 Unknown.
* @since 3.37.7 Use correct argument order for implode to fix php 7.4 deprecation.
*
* @return string
*/
public function get_table_html() {
$classes = $this->get_table_classes();
ob_start();
?>
<div class="llms-table-wrap">
<header class="llms-table-header">
<?php echo $this->get_table_title_html(); ?>
<?php if ( $this->is_searchable ) : ?>
<?php echo $this->get_table_search_form_html(); ?>
<?php endif; ?>
<?php if ( $this->is_filterable ) : ?>
<?php echo $this->get_table_filters_html(); ?>
<?php endif; ?>
</header>
<table
class="<?php echo implode( ' ', $classes ); ?>"
data-args='<?php echo wp_json_encode( $this->get_args() ); ?>'
data-handler="<?php echo $this->get_handler(); ?>"
id="llms-gb-table-<?php echo $this->id; ?>"
>
<?php echo $this->get_thead_html(); ?>
<?php echo $this->get_tbody_html(); ?>
<?php echo $this->get_tfoot_html(); ?>
</table>
</div>
<?php
return ob_get_clean();
}
/**
* Get the HTML of the search form for a searchable table.
*
* @since 3.2.0
*
* @return string
*/
public function get_table_search_form_html() {
ob_start();
?>
<div class="llms-table-search">
<input class="regular-text" id="<?php echo $this->id; ?>-search-input" placeholder="<?php echo $this->get_table_search_form_placeholder(); ?>" type="text">
</div>
<?php
return ob_get_clean();
}
/**
* Get the Text to be used as the placeholder in a searchable tables search input.
*
* @since 3.2.0
* @since 3.15.0 Fix filter name.
*
* @return string
*/
public function get_table_search_form_placeholder() {
/**
* Filters the text to be used as the placeholder in a searchable tables search input.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param string $text Text to be used as the placeholder in a searchable tables search input.
*/
return apply_filters( "llms_table_get_{$this->id}_search_placeholder", __( 'Search', 'lifterlms' ) );
}
/**
* Get the HTML for the table's title.
*
* @since 3.2.0
* @since 3.15.0 Unknown.
*
* @return string
*/
public function get_table_title_html() {
$title = $this->get_title();
if ( $title ) {
return '<h2 class="llms-table-title">' . $title . '</h2>';
} else {
return '';
}
}
/**
* Get `$this->tbody_data` array.
* @since 3.2.0
* @since 3.15.0 Fix filter name.
*
* @return array
*/
public function get_tbody_data() {
/**
* Filters the array of tbody data.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param array $tbody_data Array of data that will be used to create the table body.
*/
return apply_filters( "llms_table_get_{$this->id}_tbody_data", $this->tbody_data );
}
/**
* Get a tbody element for the table.
*
* @since 3.2.0
*
* @return string
*/
public function get_tbody_html() {
$data = $this->get_tbody_data();
ob_start();
?>
<tbody>
<?php if ( $data ) : ?>
<?php foreach ( $data as $row ) : ?>
<?php echo $this->get_tr_html( $row ); ?>
<?php endforeach; ?>
<?php else : ?>
<tr><td class="llms-gb-table-empty" colspan="<?php echo $this->get_columns_count(); ?>"><p><?php echo $this->get_empty_message(); ?></p></td></tr>
<?php endif; ?>
</tbody>
<?php
return ob_get_clean();
}
/**
* Get a tfoot element for the table.
*
* @since 3.2.0
* @since 3.28.0 Unknown.
*
* @return string
*/
public function get_tfoot_html() {
ob_start();
?>
<tfoot>
<tr>
<th colspan="<?php echo $this->get_columns_count(); ?>">
<?php if ( $this->is_exportable ) : ?>
<div class="llms-table-export">
<button class="llms-button-primary small" name="llms-table-export">
<span class="dashicons dashicons-download"></span> <?php _e( 'Export', 'lifterlms' ); ?>
</button>
<?php echo $this->get_progress_bar_html( 0 ); ?>
<em><small class="llms-table-export-msg"></small></em>
</div>
<?php endif; ?>
<?php if ( $this->is_paginated ) : ?>
<div class="llms-table-pagination">
<?php if ( $this->max_pages ) : ?>
<span class="llms-table-page-count"><?php printf( _x( '%1$d of %2$d', 'pagination', 'lifterlms' ), $this->current_page, $this->max_pages ); ?></span>
<?php endif; ?>
<?php if ( 1 !== $this->get_current_page() ) : ?>
<?php if ( $this->max_pages ) : ?>
<button class="llms-button-primary small" data-page="1" name="llms-table-paging"><span class="dashicons dashicons-arrow-left-alt"></span> <?php _e( 'First', 'lifterlms' ); ?></button>
<?php endif; ?>
<button class="llms-button-primary small" data-page="<?php echo $this->current_page - 1; ?>" name="llms-table-paging"><span class="dashicons dashicons-arrow-left-alt2"></span> <?php _e( 'Back', 'lifterlms' ); ?></button>
<?php endif; ?>
<?php if ( ! $this->is_last_page ) : ?>
<button class="llms-button-primary small" data-page="<?php echo $this->current_page + 1; ?>" name="llms-table-paging"><?php _e( 'Next', 'lifterlms' ); ?> <span class="dashicons dashicons-arrow-right-alt2"></span></button>
<?php if ( $this->max_pages ) : ?>
<button class="llms-button-primary small" data-page="<?php echo $this->max_pages; ?>" name="llms-table-paging"><?php _e( 'Last', 'lifterlms' ); ?> <span class="dashicons dashicons-arrow-right-alt"></span></button>
<?php endif; ?>
<?php endif; ?>
</div>
<?php endif; ?>
</th>
</tr>
</tfoot>
<?php
return ob_get_clean();
}
/**
* Get a thead element for the table.
*
* @since 3.2.0
*
* @return string
*/
public function get_thead_html() {
ob_start();
?>
<thead>
<tr>
<?php foreach ( $this->get_columns() as $id => $data ) : ?>
<th class="<?php echo $id; ?>">
<?php if ( is_array( $data ) ) : ?>
<?php if ( isset( $data['sortable'] ) && $data['sortable'] ) : ?>
<a class="llms-sortable<?php echo ( $this->get_orderby() === $id ) ? ' active' : ''; ?>" data-order="<?php echo $this->get_new_order( $id ); ?>" data-orderby="<?php echo $id; ?>" href="#llms-gb-table-resort">
<?php echo $data['title']; ?>
<span class="dashicons dashicons-arrow-up asc"></span>
<span class="dashicons dashicons-arrow-down desc"></span>
</a>
<?php else : ?>
<?php echo $data['title']; ?>
<?php endif; ?>
<?php else : ?>
<?php echo $data; ?>
<?php endif; ?>
</th>
<?php endforeach; ?>
</tr>
</thead>
<?php
return ob_get_clean();
}
/**
* Get a CSS class list (as a string) for each TR.
*
* @since 3.24.0
*
* @param mixed $row Object/array of data that the function can use to extract the data.
* @return string
*/
protected function get_tr_classes( $row ) {
/**
* Filters the CSS class of a table row.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.24.0
*
* @param string $class CSS class list (as a string) for a given TR.
* @param mixed $row Object/array of data that the function can use to extract the data.
*/
return apply_filters( "llms_table_get_{$this->id}_tr_classes", 'llms-table-tr', $row );
}
/**
* Get the HTML for a single row in the body of the table.
*
* @since 3.2.0
* @since 3.21.0 Fix action hooks names.
*
* @param mixed $row Array/object of data describing a single row in the table.
* @return string
*/
public function get_tr_html( $row ) {
ob_start();
/**
* Fired before a table `<tr>`.
*
* @since 3.21.0
*
* @param string $row Array/object of data describing a single row in the table.
* @param LLMS_Admin_Table $table_object Instance of the class extending `LLMS_Admin_Table`.
*/
do_action( 'llms_table_before_tr', $row, $this );
?>
<tr class="<?php echo esc_attr( $this->get_tr_classes( $row ) ); ?>">
<?php foreach ( $this->get_columns() as $id => $title ) : ?>
<td class="<?php echo $id; ?>"><?php echo $this->get_data( $id, $row ); ?></td>
<?php endforeach; ?>
</tr>
<?php
/**
* Fired after a table `<tr>`.
*
* @since 3.21.0
*
* @param string $row Array/object of data describing a single row in the table.
* @param LLMS_Admin_Table $table_object Instance of the class extending `LLMS_Admin_Table`.
*/
do_action( 'llms_table_after_tr', $row, $this );
return ob_get_clean();
}
/**
* Get the total number of columns in the table.
*
* Useful for creating full width tds via colspan.
*
* @since 3.2.0
*
* @return int
*/
public function get_columns_count() {
return count( $this->get_columns() );
}
/**
* Get the HTML to output a progress bar within a td.
*
* Improve ugly tables with a small visual flourish.
* Useful when displaying a percentage within a table!
* Bonus if the table sorts by that percentage column.
*
* @since 3.4.1
*
* @param float $percentage The percentage to be displayed.
* @param string $text Text to display over the progress bar, defaults to $percentage.
* @return string
*/
public function get_progress_bar_html( $percentage, $text = '' ) {
$text = $text ? $text : $percentage . '%';
return '<div class="llms-table-progress">
<div class="llms-table-progress-bar"><div class="llms-table-progress-inner" style="width:' . $percentage . '%"></div></div>
<span class="llms-table-progress-text">' . $text . '</span>
</div>';
}
/**
* Get the HTML for a WP Post Link.
*
* @since 3.2.0
*
* @param int $post_id WP Post ID.
* @param string $text Optional text to display within the anchor, if none supplied $post_id if used.
* @return string
*/
public function get_post_link( $post_id, $text = '' ) {
if ( ! $text ) {
$text = $post_id;
}
return '<a href="' . esc_url( get_edit_post_link( $post_id ) ) . '">' . $text . '</a>';
}
/**
* Get the title of the table.
*
* @since 3.15.0
*
* @return string
*/
public function get_title() {
/**
* Filters the table title.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param string $title The title of the table.
*/
return apply_filters( "llms_table_get_{$this->id}_table_title", $this->title );
}
/**
* Get the HTML for a WP User Link.
*
* @since 3.17.2
*
* @param int $user_id WP User ID.
* @param string $text Optional text to display within the anchor, if none supplied $user_id if used.
* @return string
*/
public function get_user_link( $user_id, $text = '' ) {
if ( ! $text ) {
$text = $user_id;
}
return '<a href="' . esc_url( get_edit_user_link( $user_id ) ) . '">' . $text . '</a>';
}
/**
* Determine if a column is visible based on the current context.
*
* @since 3.15.0
*
* @param array $data Array of a single column's data from `set_columns()`.
* @param string $context Context [display|export].
* @return bool
*/
private function is_col_visible( $data, $context = 'display' ) {
// Display if 'export_only' does not exist or it does exist and is false.
if ( 'display' === $context ) {
return ( ! isset( $data['export_only'] ) || ! $data['export_only'] );
// Display if exportable is set and is true.
} elseif ( 'export' === $context ) {
return ( isset( $data['exportable'] ) && $data['exportable'] );
}
return true;
}
/**
* Return protected is_last_page var.
*
* @since 3.15.0
*
* @return bool
*/
public function is_last_page() {
return $this->is_last_page;
}
/**
* Allow custom hooks to be registered for use within the class.
*
* @since 3.2.0
*
* @return void
*/
protected function register_hooks() {}
/**
* Setter.
*
* @since 2.3.0
*
* @param string $key Variable name.
* @param mixed $val Variable data.
* @return void
*/
public function set( $key, $val ) {
$this->$key = $val;
}
/**
* Empty message displayed when no results are found.
*
* @since 3.2.0
* @since 3.15.0 Fix filter name.
*
* @return string
*/
protected function set_empty_message() {
/**
* Filters the default message displayed when the table is empty.
*
* The dynamic portion of this filter `{$this->id}` refers to the unique ID for the table.
*
* @since 3.15.0
*
* @param string $columns The default message displayed when the table is empty.
*/
return apply_filters( 'llms_table_default_empty_message', __( 'No results were found.', 'lifterlms' ) );
}
/**
* Stub used to set the title during table construction.
*
* @since 3.28.0
*
* @return string
*/
protected function set_title() {
return '';
}
}