*
* @uses List_Trait
*
* @since 4.3.0
*/
class Snippets_List extends Abstract_List {
use List_Trait;
public const PAGE_SLUG = ADVAN_INNER_SLUG . '_page_advan_snippets';
private const HELP_URL = 'https://0-day-analytics.com/snippets-module-user-guide/?user_help=view';
public const SCREEN_OPTIONS_SLUG = 'advanced_analytics_snippets_list';
public const SEARCH_INPUT = 's';
public const MENU_SLUG = 'advan_snippets';
public const NONCE_NAME = 'advan_manage_snippets';
public const SAVE_ACTION = 'advan_snippet_save';
public const DELETE_ACTION = 'advan_snippet_delete';
public const EXECUTE_ACTION = 'advan_snippet_execute';
public const TRASH_ACTION = 'advan_snippet_trash';
public const RESTORE_ACTION = 'advan_snippet_restore';
public const CLONE_ACTION = 'advan_snippet_clone';
public const TOGGLE_ACTION = 'advan_snippet_toggle';
private const BULK_FIELD = 'aadvana_snippet_ids';
/**
* Default rows per page.
*
* @var int
*/
protected static $rows_per_page = 20;
/**
* Related entity class.
*
* @var string
*/
protected static $entity = Snippet_Entity::class;
/**
* Default order by column.
*
* @var string
*/
protected static $default_order_by = 'updated_at';
/**
* Cached admin columns map.
*
* @var array
*/
protected static $admin_columns = array();
/**
* Cached items count used by parent table class.
*
* @var int
*/
protected $count = 0;
/**
* Constructor.
*/
public function __construct() {
Snippet_Entity::create_table();
parent::__construct(
array(
'plural' => Snippet_Entity::get_table_name(),
'singular' => 'aadvana_snippet',
'ajax' => false,
)
);
}
/**
* Register admin-post endpoints.
*/
public static function init(): void {
\add_action( 'admin_post_' . self::SAVE_ACTION, array( Snippets_View::class, 'handle_save' ) );
\add_action( 'admin_post_' . self::DELETE_ACTION, array( Snippets_View::class, 'handle_delete' ) );
\add_action( 'admin_post_' . self::EXECUTE_ACTION, array( Snippets_View::class, 'handle_execute' ) );
\add_action( 'admin_post_' . self::TRASH_ACTION, array( Snippets_View::class, 'handle_trash' ) );
\add_action( 'admin_post_' . self::RESTORE_ACTION, array( Snippets_View::class, 'handle_restore' ) );
\add_action( 'admin_post_' . self::CLONE_ACTION, array( Snippets_View::class, 'handle_clone' ) );
\add_action( 'admin_post_' . self::TOGGLE_ACTION, array( Snippets_View::class, 'handle_toggle' ) );
}
/**
* Adds snippets submenu under plugin menu.
*/
public static function menu_add(): void {
$hook = \add_submenu_page(
Logs_List::MENU_SLUG,
\__( 'Snippets', '0-day-analytics' ),
\__( 'Snippets', '0-day-analytics' ),
'manage_options',
self::MENU_SLUG,
array( Snippets_View::class, 'render_page' ),
12
);
self::add_screen_options( $hook );
\add_filter( 'manage_' . $hook . '_columns', array( __CLASS__, 'manage_columns' ) );
\add_action( 'load-' . $hook, array( Settings::class, 'aadvana_common_help' ) );
\add_action( 'load-' . $hook, array( __CLASS__, 'process_actions_load' ) );
\add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_code_editor' ) );
}
/**
* Load CodeMirror for snippet form when needed.
*
* @param string $hook_suffix Current admin page hook suffix.
*
* @return void
*
* @since 4.3.0
*/
public static function maybe_enqueue_code_editor( string $hook_suffix ): void {
$valid = array( self::PAGE_SLUG, self::PAGE_SLUG . '-network' );
if ( ! in_array( $hook_suffix, $valid, true ) ) {
return;
}
$action = isset( $_GET['action'] ) ? \sanitize_key( \wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! in_array( $action, array( 'add', 'edit' ), true ) ) {
return;
}
self::enqueue_snippet_assets();
$settings = \wp_enqueue_code_editor(
array(
'type' => 'text/x-php',
'codemirror' => array(
'indentUnit' => 4,
'tabSize' => 4,
'theme' => 'cobalt',
),
)
);
if ( false !== $settings ) {
\wp_add_inline_script(
'code-editor',
sprintf(
'jQuery(function(){ wp.codeEditor.initialize( "snippet-code", %s ); });',
\wp_json_encode( $settings )
)
);
\wp_add_inline_style(
'code-editor',
'.CodeMirror-wrap { width: 99%; border: 1px solid #8c8f94; border-radius: 3px; overflow: hidden; } .CodeMirror-gutters { background: transparent; }'
);
}
}
/**
* Enqueue form specific assets.
*/
private static function enqueue_snippet_assets(): void {
\wp_enqueue_style(
'advan-snippets-editor',
\ADVAN_PLUGIN_ROOT_URL . 'css/admin/snippets-editor.css',
array(),
\ADVAN_VERSION
);
\wp_enqueue_script(
'advan-snippets-editor',
\ADVAN_PLUGIN_ROOT_URL . 'js/admin/snippets-editor.js',
array( 'jquery' ),
\ADVAN_VERSION,
true
);
\wp_localize_script(
'advan-snippets-editor',
'advanSnippetEditor',
array(
'emptyCodeMessage' => \__( 'Please add code before saving.', '0-day-analytics' ),
'sidebarCollapsed' => \__( 'Expand sidebar', '0-day-analytics' ),
'sidebarExpanded' => \__( 'Collapse sidebar', '0-day-analytics' ),
'storageKey' => 'advanSnippetSidebarState',
)
);
}
/**
* Process pending table actions early on load.
*/
public static function process_actions_load(): void {
if ( ! \current_user_can( 'manage_options' ) ) {
return;
}
$table = new self();
$table->handle_table_actions();
}
/**
* Prepare list items.
*
* @return void
*
* @since 4.3.0
*/
public function prepare_items(): void {
$search = self::escaped_search_input();
$per_page = self::get_screen_option_per_page();
$orderby = isset( $_GET['orderby'] ) ? \sanitize_key( \wp_unslash( $_GET['orderby'] ) ) : static::$default_order_by;
$order = isset( $_GET['order'] ) ? \sanitize_key( \wp_unslash( $_GET['order'] ) ) : 'DESC';
$page = $this->get_pagenum();
$offset = $per_page * ( $page - 1 );
$status = self::get_status_filter_value();
$type = self::get_type_filter_value();
$data = $this->fetch_table_data(
array(
'offset' => $offset,
'per_page' => $per_page,
'orderby' => $orderby,
'order' => $order,
'search' => $search,
'status' => $status,
'type' => $type,
)
);
$this->items = $data['items'];
$this->count = $data['total'];
$columns = self::manage_columns( array() );
$hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false );
if ( ! $hidden ) {
$hidden = array();
}
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->set_pagination_args(
array(
'total_items' => $this->count,
'per_page' => $per_page,
'total_pages' => ( $per_page > 0 ) ? (int) ceil( $this->count / $per_page ) : 1,
)
);
}
/**
* Fetch data for current request.
*
* @param array $args Query arguments.
*
* @return array{items:array,total:int}
*
* @since 4.3.0
*/
public function fetch_table_data( array $args = array() ) {
$defaults = array(
'offset' => 0,
'per_page' => self::get_screen_option_per_page(),
'orderby' => static::$default_order_by,
'order' => 'DESC',
'search' => '',
'status' => '',
'type' => '',
);
$args = \wp_parse_args( $args, $defaults );
$wpdb = Snippet_Entity::get_connection();
$table = Snippet_Entity::get_table_name( $wpdb );
$where = array( 'blog_id = %d' );
$bindings = array( \get_current_blog_id() );
if ( '' !== $args['search'] ) {
$like = '%' . $wpdb->esc_like( $args['search'] ) . '%';
$where[] = '( name LIKE %s OR tags LIKE %s )';
$bindings[] = $like;
$bindings[] = $like;
}
$status_map = array(
'enabled' => Snippet_Entity::STATUS_ENABLED,
'disabled' => Snippet_Entity::STATUS_DISABLED,
'trash' => Snippet_Entity::STATUS_TRASHED,
);
if ( isset( $status_map[ $args['status'] ] ) ) {
$where[] = 'status = %d';
$bindings[] = $status_map[ $args['status'] ];
} else {
$where[] = 'status >= %d';
$bindings[] = Snippet_Entity::STATUS_DISABLED;
}
$types = array_keys( Snippet_Entity::get_supported_types() );
if ( '' !== $args['type'] && in_array( $args['type'], $types, true ) ) {
$where[] = 'type = %s';
$bindings[] = $args['type'];
}
$where_sql = $where ? 'WHERE ' . implode( ' AND ', $where ) : '';
$count_sql = 'SELECT COUNT(*) FROM ' . $table . ' ' . $where_sql;
$total = (int) Snippet_Entity::get_var( $wpdb->prepare( $count_sql, $bindings ) );
$order = self::get_order( $args['order'] );
$orderby = self::get_order_by( $args['orderby'] );
$list_sql = 'SELECT * FROM ' . $table . ' ' . $where_sql . ' ORDER BY ' . $orderby . ' ' . $order . ' LIMIT %d OFFSET %d';
$list_items = Snippet_Entity::get_results(
$wpdb->prepare(
$list_sql,
array_merge( $bindings, array( (int) $args['per_page'], (int) $args['offset'] ) )
)
);
return array(
'items' => $list_items ?: array(),
'total' => $total,
);
}
/**
* Default column renderer.
*
* @param array $item The current item.
* @param string $column_name The name of the column.
* @return string Rendered column content.
*
* @since 4.3.0
*/
protected function column_default( $item, $column_name ) {
switch ( $column_name ) {
case 'type':
$types = Snippet_Entity::get_supported_types();
return isset( $types[ $item['type'] ] ) ? \esc_html( $types[ $item['type'] ] ) : \esc_html( $item['type'] );
case 'status':
return $this->format_status_label( (int) $item['status'] );
case 'execution_scope':
return $this->format_scope_label( (string) ( $item['execution_scope'] ?? Snippet_Entity::SCOPE_EVERYWHERE ) );
case 'execution_hook':
$hook = trim( (string) ( $item['execution_hook'] ?? '' ) );
return '' === $hook ? '—' : '' . \esc_html( $hook ) . '';
case 'hook_priority':
$priority = (int) ( $item['hook_priority'] ?? 10 );
if ( $priority <= 0 ) {
$priority = 10;
}
return \esc_html( (string) $priority );
case 'shortcode_tag':
$tag = trim( (string) ( $item['shortcode_tag'] ?? '' ) );
return '' === $tag ? '—' : '[' . \esc_html( $tag ) . ']';
case 'tags':
return $this->format_tags( $item['tags'] ?? '' );
case 'last_run_at':
return $this->format_timestamp( $item['last_run_at'] );
case 'last_run_status':
return \esc_html( $item['last_run_status'] ?? '' );
case 'updated_at':
return $this->format_timestamp( $item['updated_at'] );
case 'blog_id':
return \esc_html( (string) ( $item['blog_id'] ?? '' ) );
default:
return isset( $item[ $column_name ] ) ? \esc_html( (string) $item[ $column_name ] ) : '';
}
}
/**
* Status views (All, Enabled, Disabled, Trash).
*
* @return array View links.
*
* @since 4.3.0
*/
public function get_views() {
$status = self::get_status_filter_value();
$counts = Snippet_Entity::get_status_counters();
$views = array();
$views['all'] = $this->format_view_link( '', \__( 'All', '0-day-analytics' ), (int) ( $counts['all'] ?? 0 ), $status );
$views['enabled'] = $this->format_view_link( 'enabled', \__( 'Enabled', '0-day-analytics' ), (int) ( $counts['enabled'] ?? 0 ), $status );
$views['disabled'] = $this->format_view_link( 'disabled', \__( 'Disabled', '0-day-analytics' ), (int) ( $counts['disabled'] ?? 0 ), $status );
$views['trash'] = $this->format_view_link( 'trash', \__( 'Trash', '0-day-analytics' ), (int) ( $counts['trashed'] ?? 0 ), $status );
return $views;
}
/**
* Helper to format a single view link.
*
* @param string $value The status value for the view link.
* @param string $label The label for the view link.
* @param int $count The count of items for the view.
* @param string $current The current status filter value.
*
* @return string The formatted view link HTML.
*
* @since 4.3.0
*/
private function format_view_link( string $value, string $label, int $count, string $current ): string {
$classes = array();
if ( ( '' === $value && '' === $current ) || ( '' !== $value && $value === $current ) ) {
$classes[] = 'current';
}
$args = array();
if ( '' !== $value ) {
$args['snippet_status'] = $value;
}
$url = self::get_admin_page_url( $args );
$label_html = sprintf( '%1$s (%2$s)', \esc_html( $label ), \number_format_i18n( $count ) );
return sprintf(
'%3$s',
\esc_url( $url ),
$classes ? ' class="' . \esc_attr( implode( ' ', $classes ) ) . '"' : '',
$label_html
);
}
/**
* Render name column with row actions.
*
* @param array $item The current item.
*
* @return string Rendered column content.
*
* @since 4.3.0
*/
protected function column_name( $item ): string {
$label = '' . \esc_html( $item['name'] ) . '';
$is_trashed = Snippet_Entity::STATUS_TRASHED === (int) $item['status'];
$context = array();
$status = self::get_status_filter_value();
if ( '' !== $status ) {
$context['snippet_status'] = $status;
}
$actions = array();
if ( ! $is_trashed ) {
$edit_url = self::get_admin_page_url(
array(
'action' => 'edit',
'snippet' => (int) $item['id'],
)
);
$clone_url = \wp_nonce_url(
\add_query_arg(
array_merge(
array(
'action' => self::CLONE_ACTION,
'snippet' => (int) $item['id'],
),
$context
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$trash_url = \wp_nonce_url(
\add_query_arg(
array_merge(
array(
'action' => self::TRASH_ACTION,
'snippet' => (int) $item['id'],
),
$context
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$is_enabled = Snippet_Entity::STATUS_ENABLED === (int) $item['status'];
$toggle_url = \wp_nonce_url(
\add_query_arg(
array_merge(
array(
'action' => self::TOGGLE_ACTION,
'snippet' => (int) $item['id'],
),
$context
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$actions['toggle'] = '' . ( $is_enabled ? \esc_html__( 'Disable', '0-day-analytics' ) : \esc_html__( 'Enable', '0-day-analytics' ) ) . '';
$actions['edit'] = '' . \esc_html__( 'Edit', '0-day-analytics' ) . '';
if ( 'php' === ( $item['type'] ?? '' ) ) {
$execute_url = \wp_nonce_url(
\add_query_arg(
array(
'action' => self::EXECUTE_ACTION,
'snippet' => (int) $item['id'],
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$actions['run'] = '' . \esc_html__( 'Execute', '0-day-analytics' ) . '';
}
$actions['clone'] = '' . \esc_html__( 'Duplicate', '0-day-analytics' ) . '';
$actions['trash'] = '' . \esc_html__( 'Move to Trash', '0-day-analytics' ) . '';
} else {
$restore_url = \wp_nonce_url(
\add_query_arg(
array_merge(
array(
'action' => self::RESTORE_ACTION,
'snippet' => (int) $item['id'],
),
$context
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$delete_url = \wp_nonce_url(
\add_query_arg(
array_merge(
array(
'action' => self::DELETE_ACTION,
'snippet' => (int) $item['id'],
),
$context
),
\admin_url( 'admin-post.php' )
),
self::NONCE_NAME
);
$actions['restore'] = '' . \esc_html__( 'Restore', '0-day-analytics' ) . '';
$actions['delete'] = '' . \esc_html__( 'Delete Permanently', '0-day-analytics' ) . '';
}
return $label . $this->row_actions( $actions );
}
/**
* Checkbox column content.
*
* @param array $item - The current item.
*
* @return string Rendered column content.
*
* @since 4.3.0
*/
protected function column_cb( $item ) {
return sprintf(
'',
(int) $item['id'],
\esc_html__( 'Select snippet', '0-day-analytics' ),
\esc_attr( self::BULK_FIELD )
);
}
/**
* Bulk actions map.
*
* @return array Bulk actions.
*
* @since 4.3.0
*/
public function get_bulk_actions() {
$status = self::get_status_filter_value();
if ( 'trash' === $status ) {
return array(
'bulk-restore' => \__( 'Restore', '0-day-analytics' ),
'bulk-delete' => \__( 'Delete Permanently', '0-day-analytics' ),
);
}
return array(
'bulk-enable' => \__( 'Enable', '0-day-analytics' ),
'bulk-disable' => \__( 'Disable', '0-day-analytics' ),
'bulk-trash' => \__( 'Move to Trash', '0-day-analytics' ),
);
}
/**
* Process bulk delete action.
*
* @return void
*
* @since 4.3.0
*/
public function handle_table_actions(): void {
if ( ! \current_user_can( 'manage_options' ) ) {
return;
}
$action = $this->current_action();
if ( ! $action ) {
return;
}
if ( ! in_array( $action, array( 'bulk-enable', 'bulk-disable', 'bulk-trash', 'bulk-restore', 'bulk-delete' ), true ) ) {
return;
}
\check_admin_referer( 'bulk-' . $this->_args['plural'] );
$ids = isset( $_REQUEST[ self::BULK_FIELD ] ) ? array_map( 'intval', (array) $_REQUEST[ self::BULK_FIELD ] ) : array();
$ids = array_filter( $ids );
if ( empty( $ids ) ) {
$this->redirect_back( 'error', array(), \__( 'Select at least one snippet.', '0-day-analytics' ) );
}
switch ( $action ) {
case 'bulk-enable':
foreach ( $ids as $id ) {
Snippet_Entity::enable_by_id( $id );
}
$this->redirect_back( 'updated', array(), \__( 'Selected snippets enabled.', '0-day-analytics' ) );
break;
case 'bulk-disable':
foreach ( $ids as $id ) {
Snippet_Entity::disable_by_id( $id );
}
$this->redirect_back( 'updated', array(), \__( 'Selected snippets disabled.', '0-day-analytics' ) );
break;
case 'bulk-trash':
foreach ( $ids as $id ) {
Snippet_Entity::trash_by_id( $id );
}
$this->redirect_back( 'trashed', array(), \__( 'Selected snippets moved to trash.', '0-day-analytics' ) );
break;
case 'bulk-restore':
foreach ( $ids as $id ) {
Snippet_Entity::restore_by_id( $id );
}
$this->redirect_back( 'restored', array(), \__( 'Selected snippets restored.', '0-day-analytics' ) );
break;
case 'bulk-delete':
foreach ( $ids as $id ) {
$snippet = Snippet_Entity::get_snippet( $id );
if ( Snippet_Entity::is_trashed( $snippet ) ) {
Snippet_Entity::delete_by_id( $id );
}
}
$this->redirect_back( 'deleted', array(), \__( 'Selected snippets deleted permanently.', '0-day-analytics' ) );
break;
}
}
/**
* Extra filters above table.
*
* @param string $which 'top' or 'bottom' placement.
*
* @return void
*
* @since 4.3.0
*/
public function extra_tablenav( $which ) {
if ( 'top' !== $which ) {
return;
}
$status = isset( $_GET['snippet_status'] ) ? \sanitize_key( \wp_unslash( $_GET['snippet_status'] ) ) : '';
$type = isset( $_GET['snippet_type'] ) ? \sanitize_key( \wp_unslash( $_GET['snippet_type'] ) ) : '';
$types = Snippet_Entity::get_supported_types();
?>
Sortable columns.
*
* @since 4.3.0
*/
public function get_sortable_columns() {
$columns = array_keys( Snippet_Entity::get_column_names_admin() );
$sortable = array();
$columns[] = 'created_at';
$columns[] = 'updated_at';
foreach ( array_unique( $columns ) as $column ) {
$sortable[ $column ] = array( $column, false );
}
return $sortable;
}
/**
* Screen-option label text.
*
* @return string
*
* @since 4.3.0
*/
private static function get_screen_per_page_title(): string {
return \__( 'Number of snippets to show', '0-day-analytics' );
}
/**
* Build columns structure for WP_List_Table.
*
* @param array $columns Existing columns.
*
* @return array Modified columns.
*
* @since 4.3.0
*/
public static function manage_columns( $columns ): array {
if ( empty( self::$admin_columns ) ) {
$table_columns = array( 'cb' => '' );
self::$admin_columns = array_merge( $table_columns, Snippet_Entity::get_column_names_admin(), $columns );
}
return self::$admin_columns;
}
/**
* Output message when table empty.
*
* @return void
*
* @since 4.3.0
*/
public function no_items(): void {
\esc_html_e( 'No snippets available yet.', '0-day-analytics' );
}
/**
* Current status filter helper.
*
* @return string
*
* @since 4.3.0
*/
private static function get_status_filter_value(): string {
return isset( $_REQUEST['snippet_status'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['snippet_status'] ) ) : '';
}
/**
* Current type filter helper.
*
* @return string
*
* @since 4.3.0
*/
private static function get_type_filter_value(): string {
return isset( $_REQUEST['snippet_type'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['snippet_type'] ) ) : '';
}
/**
* Helper: format enabled/disabled label.
*
* @param int $status Status value.
*
* @return string Formatted status label.
*
* @since 4.3.0
*/
private function format_status_label( int $status ): string {
if ( Snippet_Entity::STATUS_TRASHED === $status ) {
return '' . \esc_html__( 'Trashed', '0-day-analytics' ) . '';
}
return $status
? '' . \esc_html__( 'Enabled', '0-day-analytics' ) . ''
: '' . \esc_html__( 'Disabled', '0-day-analytics' ) . '';
}
/**
* Helper: format execution scope label.
*
* @param string $scope Scope slug.
*
* @return string Human readable scope label.
*/
private function format_scope_label( string $scope ): string {
$scopes = Snippet_Entity::get_execution_scopes();
return isset( $scopes[ $scope ] ) ? \esc_html( $scopes[ $scope ] ) : \esc_html( ucwords( str_replace( '_', ' ', $scope ) ) );
}
/**
* Helper: format csv tags.
*
* @param string $csv Comma separated tags.
*
* @return string Formatted tags.
*
* @since 4.3.0
*/
private function format_tags( string $csv ): string {
if ( '' === trim( $csv ) ) {
return '—';
}
$tags = array_filter( array_map( 'sanitize_text_field', explode( ',', $csv ) ) );
return \esc_html( implode( ', ', $tags ) );
}
/**
* Human readable timestamp.
*
* @param string|null $timestamp Timestamp string.
*
* @return string Formatted timestamp.
*
* @since 4.3.0
*/
private function format_timestamp( ?string $timestamp ): string {
if ( empty( $timestamp ) ) {
return '—';
}
$time = strtotime( $timestamp . ' UTC' );
if ( ! $time ) {
return \esc_html( $timestamp );
}
$diff = \human_time_diff( $time, \time() );
return \esc_html(
sprintf(
\__( '%s ago', '0-day-analytics' ),
$diff
)
);
}
/**
* Redirect helper after actions.
*
* @param string $notice Notice type.
* @param array $extra Extra query args.
* @param string $message Optional custom message.
*
* @return void
*
* @since 4.3.0
*/
private function redirect_back( string $notice, array $extra = array(), string $message = '' ): void {
$params = array(
'snippet_notice' => $notice,
self::SEARCH_INPUT => self::escaped_search_input(),
);
if ( '' === $message && isset( $extra['_msg'] ) ) {
$message = $extra['_msg'];
unset( $extra['_msg'] );
}
if ( '' !== $message ) {
$params['_msg'] = \rawurlencode( $message );
}
$status_filter = self::get_status_filter_value();
if ( '' !== $status_filter && ! isset( $extra['snippet_status'] ) ) {
$params['snippet_status'] = $status_filter;
}
if ( ! empty( $extra ) ) {
$params = array_merge( $params, $extra );
}
\wp_safe_redirect( self::get_admin_page_url( $params ) );
exit;
}
/**
* Base admin url helper respecting network context.
*
* @return string
*
* @since 4.3.0
*/
private static function get_admin_page_base_url(): string {
return \is_network_admin() ? \network_admin_url( 'admin.php' ) : \admin_url( 'admin.php' );
}
/**
* Generates the table navigation above or below the table
*
* @param string $which - Holds info about the top and bottom navigation.
*
* @since 4.9.0
*/
public function display_tablenav( $which ) {
if ( 'top' === $which ) {
?>
';
$this->single_row_columns( $item );
echo '';
}
/**
* Build menu url with query args.
*
* @param array $args Query arguments.
*
* @return string
*
* @since 4.3.0
*/
public static function get_admin_page_url( array $args = array() ): string {
$defaults = array( 'page' => self::MENU_SLUG );
return \add_query_arg( array_merge( $defaults, $args ), self::get_admin_page_base_url() );
}
}
}