<?php /** * Admin Table Abstract * * @package LifterLMS/Abstracts/Classes * * @since 3.2.0 * @version 3.37.7 */ 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. * * Only applicable if $filterby is set. * * @var string */ protected $filter = ''; /** * Field results are filtered by. * * @var string */ protected $filterby = ''; /** * Is the Table Exportable? * * @var boolean */ protected $is_exportable = false; /** * When pagination enabled, determines if this is the last page of results. * * @var boolean */ protected $is_last_page = true; /** * If true, tfoot will add ajax pagination links. * * @var boolean */ protected $is_paginated = false; /** * Determine if the table is filterable. * * @var boolean */ 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 boolean */ protected $is_searchable = false; /** * If true, tbody will be zebra striped. * * @var boolean */ protected $is_zebra = true; /** * If an integer supplied, used to jump to last page. * * @var int */ protected $max_pages = null; /** * Results sort order. * * 'ASC' or 'DESC'. * Only applicable of $orderby is not set. * * @var string */ 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. * * Array of objects or arrays. * Each item represents as row in the table's body, each item is a cell. * * @var array */ protected $tbody_data = array(); /** * Table Title Displayed on Screen. * * @var string */ protected $title = ''; /** * Retrieve data for a cell. * * @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 * @since 3.2.0 * @version 3.2.0 */ abstract protected function get_data( $key, $data ); /** * Execute a query to retrieve results from the table. * * @param array $args array of query args * @return void * @since 3.2.0 * @version 3.2.0 */ abstract public function get_results( $args = array() ); /** * Define the structure of arguments used to pass to the get_results method. * * @return array * @since 2.3.0 * @version 2.3.0 */ abstract public function set_args(); /** * Define the structure of the table. * * @return array * @since 3.2.0 * @version 3.2.0 */ abstract protected function set_columns(); /** * Constructor. * * @since 3.2.0 * @version 3.28.0 */ 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. * * @param array $args array of arguments * @return array * @since 3.2.0 * @version 3.2.0 */ 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. * * @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 * @since 3.2.0 * @version 3.17.6 */ protected function filter_get_data( $value, $key, $data, $context = 'display' ) { return apply_filters( 'llms_table_get_data_' . $this->id, $value, $key, $data, $context, $this ); } /** * Retrieve the arguments defined in `set_args`. * * @return array * @since 3.2.0 * @version 3.15.0 */ 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 ); return apply_filters( 'llms_table_get_args_' . $this->id, $args ); } /** * Retrieve the array of columns defined by set_columns. * * @return array * @since 3.2.0 * @version 3.24.0 */ public function get_columns( $context = 'display' ) { $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. * * @return int * @since 3.2.0 * @version 3.2.0 */ public function get_current_page() { return $this->current_page; } /** * Get $this->empty_msg string. * * @return string * @since 3.2.0 * @version 3.15.0 */ public function get_empty_message() { 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. * * @param string $column_id id of the column * @return string * @since 3.4.0 * @version 3.15.0 */ 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_strinp( $column_data ) ) { $placeholder = sprintf( __( 'Any %s', 'lifterlms' ), $column_data ); } return apply_filters( 'llms_table_get_' . $this->id . '_filter_placeholder', $placeholder, $column_id ); } /** * Get the current filter. * * @return string * @since 3.4.0 * @version 3.4.0 */ public function get_filter() { return $this->filter; } /** * Get the current field results are filtered by. * * @return string * @since 3.4.0 * @version 3.4.0 */ public function get_filterby() { return $this->filterby; } /** * Retrieve a modified classname that can be passed via AJAX for new queries. * * @return string * @since 3.2.0 * @version 3.2.0 */ public function get_handler() { return str_replace( 'LLMS_Table_', '', get_class( $this ) ); } /** * Retrieve the max number of pages for the table. * * @return int * @since 3.15.0 * @version 3.15.0 */ public function get_max_pages() { return $this->max_pages; } /** * Get the current sort order. * * @return string * @since 3.2.0 * @version 3.2.0 */ public function get_order() { return $this->order; } /** * Get the current field results are ordered by. * * @return string * @since 3.2.0 * @version 3.2.0 */ public function get_orderby() { return $this->orderby; } /** * Get the current number of results to display per page. * * @return int * @since 3.28.0 * @version 3.28.0 */ 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. * * @return string * @since 3.2.0 * @version 3.2.0 */ 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. * * @return string * @since 3.2.0 * @version 3.2.0 */ 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. * * @return string * @since 3.4.0 * @version 3.4.0 */ 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 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. * * @return string * @since 3.2.0 * @version 3.2.0 */ 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. * * @return string * @since 3.2.0 * @version 3.15.0 */ public function get_table_search_form_placeholder() { return apply_filters( 'llms_table_get_' . $this->id . '_search_placeholder', __( 'Search', 'lifterlms' ) ); } /** * Get the HTML for the table's title. * * @return string * @since 3.2.0 * @version 3.15.0 */ 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. * * @return array * @since 3.2.0 * @version 3.15.0 */ public function get_tbody_data() { return apply_filters( 'llms_table_get_' . $this->id . '_tbody_data', $this->tbody_data ); } /** * Get a tbody element for the table. * * @return string * @since 3.2.0 * @version 3.2.0 */ 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. * * @return string * @since 3.2.0 * @version 3.28.0 */ 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. * * @return string * @since 3.2.0 * @version 3.2.0 */ 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. * * @param mixed $row object / array of data that the function can use to extract the data * @return string * @since 3.24.0 * @version 3.24.0 */ protected function get_tr_classes( $row ) { 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. * * @param mixed $row array/object of data describing a single row in the table * @return string * @since 3.2.0 * @version 3.21.0 */ public function get_tr_html( $row ) { ob_start(); 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 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. * * @return int * @since 3.2.0 * @version 3.2.0 */ 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. * * @param float $percentage the percentage to be displayed * @param string $text text to display over the progress bar, defaults to $percentage * @return string * @since 3.4.1 * @version 3.4.1 */ public function get_progress_bar_html( $percentage, $text = '' ) { $text = $text ? $text : $percentage . '%'; return '<div class="llms-table-progress"> <span class="llms-table-progress-text">' . $text . '</span> <div class="llms-table-progress-inner" style="width:' . $percentage . '%"></div> </div>'; } /** * Get the HTML for a WP Post Link. * * @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 * @since 3.2.0 * @version 3.2.0 */ 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. * * @return string * @since 3.15.0 * @version 3.15.0 */ public function get_title() { return apply_filters( 'llms_table_get_' . $this->id . '_table_title', $this->title ); } /** * Get the HTML for a WP User Link. * * @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 * @since 3.17.2 * @version 3.17.2 */ 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. * * @param [type] $data array of a single column's data from set_columns() * @param string $context context [display|export] * @return bool * @since 3.15.0 * @version 3.15.0 */ 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. * * @return bool * @since 3.15.0 * @version 3.15.0 */ public function is_last_page() { return $this->is_last_page; } /** * Allow custom hooks to be registered for use within the class. * * @return void * @since 3.2.0 * @version 3.2.0 */ protected function register_hooks() {} /** * Setter. * * @param string $key variable name * @param mixed $val variable data * @since 2.3.0 * @version 2.3.0 */ public function set( $key, $val ) { $this->$key = $val; } /** * Empty message displayed when no results are found. * * @return string * @since 3.2.0 * @version 3.15.0 */ protected function set_empty_message() { return apply_filters( 'llms_table_default_empty_message', __( 'No results were found.', 'lifterlms' ) ); } /** * Stub used to set the title during table construction. * * @return string * @since 3.28.0 * @version 3.28.0 */ protected function set_title() { return ''; } }