File "WPDA_List_Table.php"
Full Path: /home/vantageo/public_html/cache/cache/cache/cache/cache/cache/.wp-cli/wp-content/plugins/wp-data-access/WPDataAccess/List_Table/WPDA_List_Table.php
File size: 110.57 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Suppress "error - 0 - No summary was found for this file" on phpdoc generation
*
* @package WPDataAccess\List_Table
*/
namespace WPDataAccess\List_Table;
use WPDataAccess\Connection\WPDADB;
use WPDataAccess\Dashboard\WPDA_Dashboard;
use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Exist;
use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Lists;
use WPDataAccess\Data_Dictionary\WPDA_List_Columns;
use WPDataAccess\Macro\WPDA_Macro;
use WPDataAccess\Plugin_Table_Models\WPDA_CSV_Uploads_Model;
use WPDataAccess\Plugin_Table_Models\WPDA_Media_Model;
use WPDataAccess\Utilities\WPDA_Import;
use WPDataAccess\Utilities\WPDA_Message_Box;
use WPDataAccess\Utilities\WPDA_Repository;
use WPDataAccess\Wordpress_Original;
use WPDataAccess\WPDA;
use WPDataProjects\WPDP;
use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
use WPDataAccess\Plugin_Table_Models\WPDP_Project_Design_Table_Model;
/**
* Class WPDA_List_Table
*
* This class extends WordPress class WP_List_Table. The WordPress WP_List_Table is contained in the package as
* advised by WordPress and might be updated in future releases.
*
* @author Peter Schulz
* @since 1.0.0
*/
class WPDA_List_Table extends Wordpress_Original\WP_List_Table {
/**
* Base table for list of all tables
*/
const LIST_BASE_TABLE = 'information_schema.tables';
/**
* Default value bulk actions enabled
*/
const DEFAULT_BULK_ACTIONS_ENABLED = true;
/**
* Default value bulk export enabled
*/
const DEFAULT_BULK_EXPORT_ENABLED = true;
/**
* Default value bulk search enabled
*/
const DEFAULT_SEARCH_BOX_ENABLED = true;
/**
* Table row number within the current response
*
* @var int
*/
protected static $list_number = 0;
/**
* Menu slug of the current page
*
* @var string
*/
protected $page;
/**
* Page title
*
* @var string
*/
protected $title;
/**
* Page subtitle
*
* @var string
*/
protected $subtitle = '';
protected $show_page_title = true;
/**
* Database schema name
*
* @var string
*/
protected $schema_name = '';
/**
* Database table name
*
* @var string
*/
protected $table_name = '';
/**
* Where clause
*
* @var string
*/
protected $where = '';
/**
* Order by clause
*
* @var string
*/
protected $orderby = '';
/**
* Columns in query
*
* @var array
*/
protected $columns_queried;
/**
* Number of rows displayed in list box
*
* @var int
*/
protected $items_per_page;
/**
* Page number
*
* @var int
*/
protected $current_page = 1;
/**
* Add search box?
*
* @var bool
*/
protected $search_box_enabled = self::DEFAULT_SEARCH_BOX_ENABLED;
/**
* Enable bulk actions?
*
* @var bool
*/
protected $bulk_actions_enabled = self::DEFAULT_BULK_ACTIONS_ENABLED;
/**
* Enable exports?
*
* @var bool
*/
protected $bulk_export_enabled = self::DEFAULT_BULK_EXPORT_ENABLED;
/**
* Show view link?
*
* @var string on|off
*/
protected $show_view_link = null;
/**
* Allow inserts?
*
* @var string on|off
*/
protected $allow_insert = null;
/**
* Allow updates?
*
* @var string on|off
*/
protected $allow_update = null;
/**
* Allow deletes?
*
* @var string on|off
*/
protected $allow_delete = null;
/**
* Hides tablenav
*
* @var bool
*/
protected $hide_navigation = false;
/**
* Reference to column list
*
* @var WPDA_List_Columns
*/
protected $wpda_list_columns;
/**
* Reference to dictionary object
*
* @var WPDA_Dictionary_Exist
*/
protected $wpda_data_dictionary;
/**
* Column headers (labels)
*
* @var array
*/
protected $column_headers;
/**
* Reference to import object
*
* @var WPDA_Import
*/
protected $wpda_import = null;
/**
* Child tab clicked (used for parent child relationships only)
*
* @var null
*/
protected $child_tab = '';
/**
* Search string (entered by user or taken from cookie)
*
* @var null|string
*/
protected $search_value = null;
/**
* Previous search string
*
* @var null|string
*/
protected $search_value_old = null;
/**
* Page number item name (default 'page_number')
*
* The name can be changed for pages on multiple levels. This is needed to get back to the right page in
* parent-child page.
*
* @var string
*/
protected $page_number_item_name = 'page_number';
/**
* Real page number
*
* @var null
*/
protected $page_number_link = '';
/**
* Page number text item
*
* @var null
*/
protected $page_number_item = '';
/**
* Make the default accessible to other classes
*/
const SEARCH_ITEM_NAME_DEFAULT = 'wpda_s';
/**
* Name of search item (WP default 's')
*
* @var string
*/
protected $search_item_name = self::SEARCH_ITEM_NAME_DEFAULT;
/**
* Name of the column containing action links
*
* Default is the first column displayed
*
* @var string
*/
protected $first_display_column = '';
/**
* Table settings
*
* @var null|object
*/
protected $wpda_table_settings = null;
/**
* Project table settings
*
* @var null|object
*/
protected $wpda_project_table_settings = null;
/**
* Show help icon if help url is available
*
* @var null|string
*/
protected $help_url = null;
/**
* Store columns in array with column name as index for fast access
*
* @var array
*/
protected $columns_indexed = array();
/**
* Variables used to store table estimate row count data
*
* @var string
*/
protected $table_engine = '';
/**
* Table rows
*
* @var mixed|string
*/
protected $table_rows = '';
/**
* Estimated row count
*
* @var array|int
*/
protected $row_count_estimate = array();
/**
* Project page id
*
* Helper to hide schema and table name in export links on web pages
*
* @var null
*/
protected $pid = null;
/**
* Cache columns
*
* @var null
*/
protected $wpda_cached_columns = null;
/**
* WPDA_List_Table constructor
*
* A table name must be provided in the constructor call. The table must be a valid MySQL database table to
* which access (to the back-end) is granted. If no table name is provided, the table does not exist or access
* to the back-end for the given table is not granted, processing is stopped. It makes no sense to continue
* without a valid table. A check for table existence is performed to prevent SQL injection.
*
* There are two types of tables to build a list table on:
* + List of tables in the WordPress database schema
* + List of rows in a specific table
*
* To build a list of tables in the WordPress database schema, we query table 'information_schema.tables'
* (which is in fact a view). This is the only query allowed outside the WordPress database schema. The
* table/view name is stored in constant WPDA_List_Table::LIST_BASE_TABLE for validation purposes.
*
* A list of rows for a specific table is based on WordPress class WP_List_Table.
*
* WPDA_List_Table can be used to build list tables for views as well. View based list tables however, do not
* support insert, update, delete, import and export actions.
*
* A table name is not the only thing we need to build a list table. We also need to have access to the
* table columns. If no table columns are provided execution is stopped as well.
*
* @param array $args [
*
* 'table_name' => (string) Database table name
*
* 'wpda_list_columns' => (object) Reference to a WPDA_List_Columns object
*
* 'singular' => (string) Singular object label
*
* 'plural' => (string) plural object label
*
* 'ajax' => (string) TRUE = list table support Ajax
*
* 'column_headers' => (array|string) Column headers
*
* 'title' => (string) Page title
*
* 'subtitle' => (string) Page subtitle
*
* 'bulk_export_enabled' => (boolean)
*
* 'search_box_enabled' => (boolean)
*
* 'bulk_actions_enabled' => (boolean)
*
* 'show_view_link' => (string) on|off
*
* 'allow_insert' => (string) on|off
*
* 'allow_update' => (string) on|off
*
* 'allow_delete' => (string) on|off
*
* 'allow_import' => (string) on|off
*
* 'hide_navigation' => (boolean)
*
* 'default_where' => (string)
*
* ]
* @since 1.0.0
*/
public function __construct( $args = array() ) {
global $wpdb;
if ( !isset( $args['table_name'] ) ) {
// Calling WPDA_List_Table without a table_name doesn't make sense.
wp_die( __( 'ERROR: Wrong arguments [no table argument]', 'wp-data-access' ) );
}
if ( !isset( $args['wpda_list_columns'] ) ) {
// Calling WPDA_List_Table without a column list is not allowed.
wp_die( __( 'ERROR: Wrong arguments [no columns argument]', 'wp-data-access' ) );
}
parent::__construct( array(
'singular' => ( isset( $args['singular'] ) ? $args['singular'] : __( 'Row', 'wp-data-access' ) ),
'plural' => ( isset( $args['plural'] ) ? $args['plural'] : __( 'Rows', 'wp-data-access' ) ),
'ajax' => ( isset( $args['ajax'] ) ? $args['ajax'] : false ),
) );
// Set schema name is available.
if ( isset( $args['wpdaschema_name'] ) ) {
$this->schema_name = $args['wpdaschema_name'];
}
// Set table name (availability already checked).
$this->table_name = $args['table_name'];
if ( self::LIST_BASE_TABLE !== $this->table_name ) {
// Whenever a table name is provided through a URL we are risking an SQL injection attack. Our
// defence mechanism here is to check whether the table name provided exists in our database.
// If it does not we'll terminate the process with an error.
// Although this check is only needed when a table name is provided through the URL we will perform
// it in all situations. It is a fast query which makes our application much more safe and reliable.
$this->wpda_data_dictionary = new WPDA_Dictionary_Exist($this->schema_name, $this->table_name);
if ( !$this->wpda_data_dictionary->table_exists() ) {
wp_die( __( 'ERROR: Invalid table name or not authorized', 'wp-data-access' ) );
}
}
$this->pid = ( isset( $args['pid'] ) ? $args['pid'] : '' );
// Get menu slag of current page.
if ( isset( $_REQUEST['page'] ) ) {
$this->page = sanitize_text_field( wp_unslash( $_REQUEST['page'] ) );
// input var okay.
} else {
// In order to show a list table we need a page.
wp_die( __( 'ERROR: Wrong arguments [no page argument]', 'wp-data-access' ) );
}
// Use column list: argument wpda_list_columns (availability already checked).
$this->wpda_list_columns =& $args['wpda_list_columns'];
// Set pagination.
if ( is_admin() ) {
$this->items_per_page = WPDA::get_option( WPDA::OPTION_BE_PAGINATION );
} else {
$this->items_per_page = WPDA::get_option( WPDA::OPTION_FE_PAGINATION );
}
// Overwrite column header text if column headers were provided.
$this->column_headers = ( isset( $args['column_headers'] ) ? $args['column_headers'] : '' );
// Set title.
if ( isset( $args['title'] ) ) {
$this->title = $args['title'];
} else {
$this->title = __( 'Table', 'wp-data-access' ) . ' ' . strtoupper( $this->table_name );
}
// Set subtitle.
if ( isset( $args['subtitle'] ) ) {
$this->subtitle = $args['subtitle'];
} else {
$wp_tables = $wpdb->tables( 'all', true );
if ( isset( $wp_tables[substr( $this->table_name, strlen( $wpdb->prefix ) )] ) ) {
$this->subtitle = '<span class="dashicons dashicons-warning"></span> ' . WPDA::get_table_type_text( WPDA::TABLE_TYPE_WP );
} elseif ( WPDA::is_wpda_table( $this->table_name ) ) {
$this->subtitle = '<span class="dashicons dashicons-warning"></span> ' . WPDA::get_table_type_text( WPDA::TABLE_TYPE_WPDA );
}
}
if ( !(isset( $args['allow_import'] ) && 'off' === $args['allow_import']) ) {
try {
// Instantiate WPDA_Import.
$this->wpda_import = new WPDA_Import(( is_admin() ? "?page={$this->page}" : '' ), $this->schema_name, $this->table_name);
} catch ( \Exception $e ) {
// If import is turned off instantiation will fail. Handle is set to null (check in future calls).
$this->wpda_import = null;
}
}
if ( isset( $args['bulk_export_enabled'] ) ) {
$this->bulk_export_enabled = $args['bulk_export_enabled'];
}
if ( isset( $args['search_box_enabled'] ) ) {
$this->search_box_enabled = $args['search_box_enabled'];
}
if ( isset( $args['bulk_actions_enabled'] ) ) {
$this->bulk_actions_enabled = $args['bulk_actions_enabled'];
}
if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK ) ) {
$this->show_view_link = WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK );
} else {
if ( isset( $args['show_view_link'] ) ) {
$this->show_view_link = $args['show_view_link'];
} else {
$this->show_view_link = WPDA::get_option( WPDA::OPTION_BE_VIEW_LINK );
}
}
if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT ) ) {
$this->allow_insert = WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT );
} else {
if ( isset( $args['allow_insert'] ) ) {
$this->allow_insert = $args['allow_insert'];
} else {
$this->allow_insert = WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT );
}
}
if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE ) ) {
$this->allow_update = WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE );
} else {
if ( isset( $args['allow_update'] ) ) {
$this->allow_update = $args['allow_update'];
} else {
$this->allow_update = WPDA::get_option( WPDA::OPTION_BE_ALLOW_UPDATE );
}
}
if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE ) ) {
$this->allow_delete = WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE );
} else {
if ( isset( $args['allow_delete'] ) ) {
$this->allow_delete = $args['allow_delete'];
} else {
$this->allow_delete = WPDA::get_option( WPDA::OPTION_BE_ALLOW_DELETE );
}
}
if ( isset( $args['hide_navigation'] ) ) {
$this->hide_navigation = $args['hide_navigation'];
}
$this->search_value = ( is_scalar( $this->get_search_value() ) ? str_replace( "\\'", '', (string) $this->get_search_value() ) : '' );
if ( isset( $_REQUEST["{$this->search_item_name}_old_value"] ) ) {
$this->search_value_old = sanitize_text_field( wp_unslash( $_REQUEST["{$this->search_item_name}_old_value"] ) );
// input var okay.
} else {
$this->search_value_old = $this->search_value;
}
// Get page number(s).
if ( 'page_number' !== $this->page_number_item_name ) {
if ( isset( $_REQUEST['page_number'] ) ) {
$requested_page_number = sanitize_text_field( wp_unslash( $_REQUEST['page_number'] ) );
// input var okay.
$this->page_number_link = '&page_number=' . $requested_page_number;
$this->page_number_item = "<input type='hidden' name='page_number' value='" . $requested_page_number . "' />";
}
}
$this->page_number_link .= '&paged=' . $this->get_pagenum();
$this->page_number_item .= "<input type='hidden' name='" . $this->page_number_item_name . "' value='" . $this->get_pagenum() . "' />";
// Add search arguments to link to return to same page.
foreach ( $_REQUEST as $key => $value ) {
if ( substr( $key, 0, 19 ) === 'wpda_search_column_' ) {
if ( is_array( $value ) ) {
foreach ( $value as $elem_key => $elem_value ) {
$this->page_number_link .= "&{$elem_key}={$elem_value}";
$this->page_number_item .= "<input type='hidden' name='{$key}[]' value='{$elem_value}' />";
}
} else {
$this->page_number_link .= "&{$key}={$value}";
$this->page_number_item .= "<input type='hidden' name='{$key}' value='{$value}' />";
}
}
}
// Check if a WHERE clause (filter) was defined.
if ( isset( $args['default_where'] ) ) {
$this->where = $args['default_where'];
}
// Get table settings.
$wpda_table_settings = WPDA_Table_Settings_Model::query( $this->table_name, $this->schema_name );
if ( isset( $wpda_table_settings[0]['wpda_table_settings'] ) ) {
$this->wpda_table_settings = json_decode( $wpda_table_settings[0]['wpda_table_settings'] );
}
// Get estimated row count.
$this->row_count_estimate = WPDA::get_row_count_estimate( $this->schema_name, $this->table_name, $this->wpda_table_settings );
$this->table_rows = $this->row_count_estimate['row_count'];
// Get project table settings.
global $wpda_project_mode;
if ( is_array( $wpda_project_mode ) ) {
$setname = $wpda_project_mode['setname'];
$wpda_project_table_settings = null;
$wpda_project_table_settings_db = WPDP_Project_Design_Table_Model::static_query( $this->schema_name, $this->table_name, $setname );
if ( isset( $wpda_project_table_settings_db->tableinfo ) ) {
$this->wpda_project_table_settings = $wpda_project_table_settings_db->tableinfo;
}
}
if ( isset( $args['show_page_title'] ) && false === $args['show_page_title'] ) {
$this->show_page_title = $args['show_page_title'];
}
if ( isset( $args['help_url'] ) ) {
$this->help_url = $args['help_url'];
}
foreach ( $this->wpda_list_columns->get_table_columns() as $table_columns ) {
$this->columns_indexed[$table_columns['column_name']] = $table_columns;
}
}
/**
* Overwrite method
*
* @return int|void
*/
public function get_pagenum() {
if ( isset( $_REQUEST[$this->page_number_item_name] ) ) {
$pagenum = ( isset( $_REQUEST[$this->page_number_item_name] ) ? absint( $_REQUEST[$this->page_number_item_name] ) : 0 );
if ( isset( $this->_pagination_args['total_pages'] ) && $pagenum > $this->_pagination_args['total_pages'] ) {
$pagenum = $this->_pagination_args['total_pages'];
}
return max( 1, $pagenum );
} else {
return parent::get_pagenum();
}
}
/**
* Set columns to be queries
*
* Set columns to be queried and shown in the list table.
* By default all columns in the table will be displayed.
*
* @param mixed $columns_queried Column list.
*
* @since 1.0.0
*/
public function set_columns_queried( $columns_queried ) {
$this->columns_queried = $columns_queried;
}
/**
* Enable or disable bulk actions
*
* @param boolean $bulk_actions_enabled TRUE = allowed, FALSE = not allowed.
*
* @since 1.0.0
*/
public function set_bulk_actions_enabled( $bulk_actions_enabled ) {
$this->bulk_actions_enabled = $bulk_actions_enabled;
}
/**
* Enable or disable bulk export options
*
* @param boolean $bulk_export_enabled TRUE = allowed, FALSE = not allowed.
*
* @since 1.0.0
*/
public function set_bulk_export_enabled( $bulk_export_enabled ) {
$this->bulk_export_enabled = $bulk_export_enabled;
}
/**
* Show or hide search box
*
* @param boolean $search_box_enabled TRUE = show search box, FALSE = do not show search bow.
*
* @since 1.0.0
*/
public function set_search_box_enabled( $search_box_enabled ) {
$this->search_box_enabled = $search_box_enabled;
}
/**
* Set page title
*
* @param string $title Page title.
*
* @since 1.0.0
*/
public function set_title( $title ) {
$this->title = $title;
}
/**
* Set page subtitle
*
* @param string $subtitle Page subtitle.
*
* @since 1.0.0
*/
public function set_subtitle( $subtitle ) {
$this->subtitle = $subtitle;
}
/**
* I18n text displayed when no data found
*
* @since 1.0.0
*/
public function no_items() {
echo __( 'No data found', 'wp-data-access' );
}
/**
* Use this method to build parent child relationships.
*
* Overwrite this function if you want to use the list table as a child list table related to some parent
* form. You can add parent arguments to calls to make sure you get back to the right parent.
*
* @param array $item Column info.
* @return string String contains parent arguments.
* @since 1.5.0
*/
protected function add_parent_args_as_string( $item ) {
return '';
}
/**
* Render columns
*
* Three links are provided for every list table row:
* + 'View' => Link to view form (readonly data entry form)
* + 'Edit' => Link to data entry form (updatable)
* + 'Delete' => Link to delete record from database table
*
* Links to view, edit and delete records are only available if a primary key for the table is found. Without
* a primary key unique identification of records is not possible.
*
* Links to action are offered through hidden forms to prevent long URL's and problem when refreshing pages
* manually. More information about how and why is provided in source code at place where I thought the
* information could be helpful.
*
* @param array $item Column info.
* @param string $column_name Column name.
*
* @return mixed Value display in list table column
* @since 1.0.0
*/
public function column_default( $item, $column_name ) {
if ( $this->wpda_list_columns->get_table_columns()[0]['column_name'] === $column_name || $column_name === $this->first_display_column ) {
// First column: add row actions.
$count = count( $this->wpda_list_columns->get_table_primary_key() );
//phpcs:ignore - 8.1 proof
if ( 0 === $count ) {
// No actions without a primary key!
// This automatically covers view processing correctly.
return $this->render_column_content( $item, $column_name ) . $this->row_actions( array(
'wpda_hide_column' => '',
) );
} else {
// Check rights.
if ( 'off' === $this->show_view_link && 'off' === $this->allow_update && 'off' === $this->allow_delete ) {
// No rights!
$actions = array();
$this->column_default_add_action( $item, $column_name, $actions );
if ( is_array( $actions ) && count( $actions ) > 0 ) {
//phpcs:ignore - 8.1 proof
return sprintf( '%1$s %2$s', $this->render_column_content( $item, $column_name ), $this->row_actions( $actions ) );
} else {
return sprintf( '%1$s', $this->render_column_content( $item, $column_name ) );
}
}
// Check if row security is enabled.
$row_security = isset( $this->wpda_table_settings->table_settings->row_level_security ) && 'true' === $this->wpda_table_settings->table_settings->row_level_security;
// To prevent our URLs containing many arguments, we'll use post to submit row actions. Since our
// list table is build within a form and we cannot use nested forms we'll use a container (id =
// wpda_invisible_container) defined outside the list table, and add our forms to that container.
// We'll use jQuery to add our forms to the container. From the links in our rows we can then just
// submit any form in that container with jQuery as well.
$form_id = '_' . self::$list_number++;
$wp_nonce_keys = '';
$row_security_keys = '';
// We need to add keys and values for multi-column primary keys.
foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
$wp_nonce_keys .= "-{$item[$key]}";
if ( $row_security ) {
$row_security_keys .= "-{$key}-{$item[$key]}";
}
}
if ( $row_security ) {
$row_security_nonce = wp_create_nonce( "wpda-row-level-security-{$this->table_name}{$row_security_keys}" );
$row_security_nonce_field = "<input type='hidden' name='rownonce' value='" . esc_attr( $row_security_nonce ) . "' />";
} else {
$row_security_nonce_field = '';
}
// Prepare url.
if ( is_admin() ) {
$url = "?page={$this->page}";
} else {
$url = '';
}
if ( 'on' === $this->show_view_link ) {
$wp_nonce_action = "wpda-query-{$this->table_name}{$wp_nonce_keys}";
$wp_nonce = wp_create_nonce( $wp_nonce_action );
// Build the row action. Use jQuery to add form to container.
// All items escaped in create_post_form().
$view_form = $this->create_post_form(
$item,
"view_form{$form_id}",
'view',
$url,
$wp_nonce,
$row_security_nonce_field,
$this->schema_name,
$this->table_name
);
?>
<script type='text/javascript'>
jQuery("#wpda_invisible_container").append("<?php
echo $view_form;
// phpcs:ignore WordPress.Security.EscapeOutput
?>");
</script>
<?php
// Add link to submit form.
$actions['view'] = sprintf(
'<a href="javascript:void(0)"
class="edit wpda_tooltip"
title="%s"
onclick="jQuery(\'#%s\').submit()">
<span style="white-space: nowrap">
<i class="fas fa-eye wpda_icon_on_button"></i>
%s
</span>
</a>
',
__( 'View row data', 'wp-data-access' ),
"view_form{$form_id}",
__( 'View', 'wp-data-access' )
);
}
if ( 'on' === $this->allow_update || WPDA::is_wpda_table( $this->table_name ) ) {
$wp_nonce_action = "wpda-query-{$this->table_name}{$wp_nonce_keys}";
$wp_nonce = wp_create_nonce( $wp_nonce_action );
// Build the row action. Use jQuery to add form to container.
// All items escaped in create_post_form().
$edit_form = $this->create_post_form(
$item,
"edit_form{$form_id}",
'edit',
$url,
$wp_nonce,
$row_security_nonce_field,
$this->schema_name,
$this->table_name
);
?>
<script type='text/javascript'>
jQuery("#wpda_invisible_container").append("<?php
echo $edit_form;
// phpcs:ignore WordPress.Security.EscapeOutput
?>");
</script>
<?php
// Add link to submit form.
$actions['edit'] = sprintf(
'<a href="javascript:void(0)"
class="edit wpda_tooltip"
title="%s"
onclick="jQuery(\'#%s\').submit()">
<span style="white-space: nowrap">
<i class="fas fa-pen wpda_icon_on_button"></i>
%s
</span>
</a>
',
__( 'Edit row data', 'wp-data-access' ),
"edit_form{$form_id}",
__( 'Edit', 'wp-data-access' )
);
}
if ( 'on' === $this->allow_delete || WPDA::is_wpda_table( $this->table_name ) ) {
$wp_nonce_action = "wpda-delete-{$this->table_name}{$wp_nonce_keys}";
$wp_nonce = wp_create_nonce( $wp_nonce_action );
// Build the row action. Use jQuery to add form to container.
// All items escaped in create_post_form().
$delete_form = $this->create_post_form(
$item,
"delete_form{$form_id}",
'delete',
$url,
$wp_nonce,
$row_security_nonce_field,
$this->schema_name,
$this->table_name
);
?>
<script type='text/javascript'>
jQuery("#wpda_invisible_container").append("<?php
echo $delete_form;
// phpcs:ignore WordPress.Security.EscapeOutput
?>");
</script>
<?php
// Add link to submit form.
$warning = __( "You are about to permanently delete these items from your site.\\nThis action cannot be undone.\\n\\'Cancel\\' to stop, \\'OK\\' to delete.", 'wp-data-access' );
$actions['delete'] = sprintf(
'<a href="javascript:void(0)"
class="delete wpda_tooltip"
title="%s"
onclick="if (confirm(\'%s\')) jQuery(\'#%s\').submit()">
<span style="white-space: nowrap">
<i class="fas fa-trash wpda_icon_on_button"></i>
%s
</span>
</a>
',
__( 'Delete row data (this cannot be undone)', 'wp-data-access' ),
$warning,
"delete_form{$form_id}",
__( 'Delete', 'wp-data-access' )
);
}
// Developers can add actions by adding their own implementation of following method.
$this->column_default_add_action( $item, $column_name, $actions );
if ( has_filter( 'wpda_column_default' ) ) {
// Use filter.
$filter = apply_filters(
'wpda_column_default',
'',
$item,
$column_name,
$this->table_name,
$this->schema_name,
$this->wpda_list_columns,
self::$list_number,
$this
);
if ( null !== $filter ) {
return sprintf( '%1$s %2$s', $filter, $this->row_actions( $actions ) );
}
}
return sprintf( '%1$s %2$s', $this->render_column_content( $item, $column_name ), $this->row_actions( $actions ) );
}
} else {
if ( substr( $column_name, 0, 15 ) === 'wpda_hyperlink_' ) {
$hyperlink_no = substr( $column_name, 15 );
if ( isset( $this->wpda_table_settings->hyperlinks[$hyperlink_no] ) ) {
$hyperlink = $this->wpda_table_settings->hyperlinks[$hyperlink_no];
$hyperlink_html = ( isset( $hyperlink->hyperlink_html ) ? $hyperlink->hyperlink_html : '' );
if ( '' !== $hyperlink_html ) {
// Substitute values.
foreach ( $item as $key => $value ) {
$hyperlink_html = str_replace( "\$\${$key}\$\$", str_replace( ' ', '%20', (string) $value ), $hyperlink_html );
}
}
$macro = new WPDA_Macro($hyperlink_html);
$hyperlink_html = $macro->exe_macro();
if ( '' !== $hyperlink_html ) {
// Link is not empty AFTER substitution.
if ( false !== strpos( ltrim( $hyperlink_html ), '<' ) ) {
return html_entity_decode( $hyperlink_html );
} else {
$hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
$hyperlink_target = ( isset( $hyperlink->hyperlink_target ) ? $hyperlink->hyperlink_target : false );
$target = ( true === $hyperlink_target ? "target='_blank'" : '' );
return "<a href='" . str_replace( ' ', '+', trim( $hyperlink_html ) ) . "' {$target}>{$hyperlink_label}</a>";
}
} else {
return '';
}
}
return 'ERROR';
} else {
// Check if column is of type media.
$media_type = WPDA_Media_Model::get_column_media( $this->table_name, $column_name, $this->schema_name );
if ( 'Image' === $media_type ) {
$image_ids = explode( ',', (string) $item[$column_name] );
//phpcs:ignore - 8.1 proof
$image_src = '';
foreach ( $image_ids as $image_id ) {
$url = wp_get_attachment_url( esc_attr( $image_id ) );
if ( false !== $url ) {
$title = get_the_title( esc_attr( $image_id ) );
$image_src .= ( '' !== $image_src ? '<br/>' : '' );
$image_src .= sprintf( '<img src="%s" class="wpda_tooltip" title="%s" width="100%%">', $url, $title );
}
}
return $image_src;
} elseif ( 'ImageURL' === $media_type ) {
return sprintf( '<img src="%s" class="wpda_tooltip" width="100%%">', $item[$column_name] );
} elseif ( 'Attachment' === $media_type ) {
$media_ids = explode( ',', (string) $item[$column_name] );
//phpcs:ignore - 8.1 proof
$media_links = '';
foreach ( $media_ids as $media_id ) {
$url = wp_get_attachment_url( esc_attr( $media_id ) );
if ( false !== $url ) {
$mime_type = get_post_mime_type( $media_id );
if ( false !== $mime_type ) {
$title = get_the_title( esc_attr( $media_id ) );
$media_links .= self::column_media_attachment( $url, $title, $mime_type );
}
}
}
return $media_links;
} elseif ( 'Hyperlink' === $media_type ) {
if ( null === $item[$column_name] || '' === $item[$column_name] ) {
return '';
} else {
if ( !(isset( $this->wpda_table_settings->table_settings->hyperlink_definition ) && 'text' === $this->wpda_table_settings->table_settings->hyperlink_definition) ) {
$hyperlink = json_decode( $item[$column_name], true );
if ( is_array( $hyperlink ) && isset( $hyperlink['label'] ) && isset( $hyperlink['url'] ) && isset( $hyperlink['target'] ) ) {
if ( '' === $hyperlink['url'] ) {
return '';
} else {
return "<a href='{$hyperlink['url']}' target='{$hyperlink['target']}'>{$hyperlink['label']}</a>";
}
} else {
return '';
}
} else {
$hyperlink_label = $this->wpda_list_columns->get_column_label( $column_name );
return "<a href='{$item[$column_name]}' target='_blank'>{$hyperlink_label}</a>";
}
}
} elseif ( 'Audio' === $media_type ) {
$audio_ids = explode( ',', (string) $item[$column_name] );
//phpcs:ignore - 8.1 proof
$audio_src = '';
foreach ( $audio_ids as $audio_id ) {
if ( 'audio' === substr( get_post_mime_type( $audio_id ), 0, 5 ) ) {
$url = wp_get_attachment_url( esc_attr( $audio_id ) );
if ( false !== $url ) {
$title = get_the_title( esc_attr( $audio_id ) );
if ( false !== $url ) {
$audio_src .= '<div title="' . $title . '" class="wpda_tooltip">' . do_shortcode( '[audio src="' . $url . '"]' ) . '</div>';
}
}
}
}
return $audio_src;
} elseif ( 'Video' === $media_type ) {
$video_ids = explode( ',', (string) $item[$column_name] );
//phpcs:ignore - 8.1 proof
$video_src = '';
foreach ( $video_ids as $video_id ) {
if ( 'video' === substr( get_post_mime_type( $video_id ), 0, 5 ) ) {
$url = wp_get_attachment_url( esc_attr( $video_id ) );
if ( false !== $url ) {
if ( false !== $url ) {
$video_src .= do_shortcode( '[video src="' . $url . '"]' );
}
}
}
}
return $video_src;
}
}
if ( has_filter( 'wpda_column_default' ) ) {
// Use filter.
$filter = apply_filters(
'wpda_column_default',
'',
$item,
$column_name,
$this->table_name,
$this->schema_name,
$this->wpda_list_columns,
self::$list_number,
$this
);
if ( null !== $filter ) {
return $filter;
}
}
if ( 'csv' !== WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) && isset( $this->columns_indexed[$column_name]['data_type'] ) && 'set' === $this->columns_indexed[$column_name]['data_type'] ) {
$list = '<' . WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) . '>';
$listarray = explode( ',', (string) $item[$column_name] );
//phpcs:ignore - 8.1 proof
foreach ( $listarray as $listitem ) {
$list .= "<li>{$listitem}</li>";
}
$list .= '</' . WPDA::get_option( WPDA::OPTION_PLUGIN_SET_FORMAT ) . '>';
return $list;
}
return $this->render_column_content( $item, $column_name );
}
}
/**
* Create post form which is added to the invisible container
*
* @param array $item Column info.
* @param string $form_id Form ID.
* @param string $action Form action.
* @param string $url URL admin page.
* @param string $wp_nonce Nonce.
* @param string $row_security_nonce_field Nonce for row level access security.
* @param string $schema_name Database schema name.
* @param string $table_name Database table name.
*
* @return array|string|string[]
*/
private function create_post_form(
$item,
$form_id,
$action,
$url,
$wp_nonce,
$row_security_nonce_field,
$schema_name,
$table_name
) {
// Already escaped: $url, $row_security_nonce_field, get_key_input_fields(), add_parent_args_as_string().
$esc_attr = 'esc_attr';
$get_key_input_fields = $this->get_key_input_fields( $item );
$add_parent_args_as_string = $this->add_parent_args_as_string( $item );
$page_number_item = $this->page_number_item;
$case_sensitive_search = ( isset( $_REQUEST['wpda_c'] ) && 'true' === $_REQUEST['wpda_c'] ? "<input type='hidden' name='wpda_c' value='true'>" : '' );
$add_schema_and_table_name = ( !is_admin() ? '' : "\n\t\t\t\t\t<input type='hidden' name='wpdaschema_name' value='{$esc_attr( $schema_name )}' />\n\t\t\t\t\t<input type='hidden' name='table_name' value='{$esc_attr( $table_name )}' />\n\t\t\t\t" );
// Hide schema and table name on front-end
$form = <<<EOT
\t\t\t\t<form id='{$esc_attr( $form_id )}' action='{$url}' method='post'>
\t\t\t\t\t{$get_key_input_fields}
\t\t\t\t\t{$add_parent_args_as_string}
\t\t\t\t\t{$add_schema_and_table_name}
\t\t\t\t\t<input type='hidden' name='action' value='{$esc_attr( $action )}' />
\t\t\t\t\t<input type='hidden' name='_wpnonce' value='{$esc_attr( $wp_nonce )}'>
\t\t\t\t\t{$row_security_nonce_field}
\t\t\t\t\t{$page_number_item}
\t\t\t\t\t{$case_sensitive_search}
\t\t\t\t</form>
EOT;
return str_replace( array("\n", "\r"), '', $form );
}
/**
* Create media link
*
* @param string $url Media URL.
* @param string $title Media title.
* @param string $mime_type Mime type.
*
* @return string
*/
public static function column_media_attachment( $url, $title, $mime_type ) {
if ( 'image' === substr( $mime_type, 0, 5 ) ) {
$class = 'dashicons-format-image';
} elseif ( 'audio' === substr( $mime_type, 0, 5 ) ) {
$class = 'dashicons-playlist-audio';
} elseif ( 'video' === substr( $mime_type, 0, 5 ) ) {
$class = 'dashicons-playlist-video';
} elseif ( 'application' === substr( $mime_type, 0, 11 ) ) {
$class = 'dashicons-media-document';
} else {
$class = 'dashicons-external';
}
return sprintf(
'<a href="%s" title="%s" target="_blank"><span class="dashicons %s wpda_attachment_icon"></span></a>',
$url,
$title,
$class
);
}
/**
* Overwrite method to prevent double row actions
*
* @param object $item Table column.
* @param string $column_name Column name.
* @param string $primary Primary key.
*
* @return string
*/
protected function handle_row_actions( $item, $column_name, $primary ) {
return '';
}
/**
* Render column content
*
* Strip content if too long and replace & character.
*
* @param object $item Table column.
* @param string $column_name Database column name.
*
* @return string Rendered column content
*/
protected function render_column_content( $item, $column_name, $substitute_newlines = true ) {
$column_content = ( isset( $item["lookup_value_{$column_name}"] ) ? $item["lookup_value_{$column_name}"] : $item[$column_name] );
if ( 'off' === WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP_SWITCH ) && WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) < strlen( (string) $column_content ) ) {
$title = sprintf( __( 'Output limited to %1$s characters', 'wp-data-access' ), WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) );
if ( $substitute_newlines ) {
return str_replace( "\n", '<br/>', substr( esc_html( str_replace( '&', '&', (string) $column_content ) ), 0, WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) ) . ' <a href="javascript:void(0)" title="' . $title . '">•••</a>' );
} else {
return substr( esc_html( str_replace( '&', '&', (string) $column_content ) ), 0, WPDA::get_option( WPDA::OPTION_BE_TEXT_WRAP ) ) . ' <a href="javascript:void(0)" title="' . $title . '">•••</a>';
}
} else {
$column_data_type = $this->wpda_list_columns->get_column_data_type( $column_name );
switch ( $column_data_type ) {
case 'date':
// date only.
if ( '' === $column_content || null === $column_content ) {
$column_content = '';
} else {
$column_content = date_i18n( get_option( 'date_format' ), strtotime( $column_content ) );
}
break;
case 'time':
// time only.
if ( '' === $column_content || null === $column_content ) {
$column_content = '';
} else {
$column_content = date_i18n( get_option( 'time_format' ), strtotime( $column_content ) );
}
break;
case 'datetime':
// date + time.
case 'timestamp':
if ( '' === $column_content || null === $column_content ) {
$column_content = '';
} else {
$column_content = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $column_content ) );
}
}
if ( $substitute_newlines ) {
return str_replace( "\n", '<br/>', esc_html( str_replace( '&', '&', (string) $column_content ) ) );
} else {
return esc_html( str_replace( '&', '&', (string) $column_content ) );
}
}
}
/**
* Generate hidden fields for keys
*
* @param array $item Column info.
*
* @return string String containing key items and values.
*/
protected function get_key_input_fields( $item ) {
$key_input_fields = '';
foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
$key_name = esc_attr( $key );
$key_value = esc_attr( $item[$key] );
$key_input_fields .= "<input type='hidden' name='{$key_name}' value='{$key_value}' />";
}
return $key_input_fields;
}
/**
* Override this method to add actions to first column of row
*
* @param array $item Item information.
* @param string $column_name Column name.
* @param array $actions Array of actions to be added to row.
*/
protected function column_default_add_action( $item, $column_name, &$actions ) {
if ( has_filter( 'wpda_column_default_add_action' ) ) {
$filter = apply_filters(
'wpda_column_default_add_action',
'',
$item,
$column_name,
$this->table_name,
$this->schema_name,
$this->wpda_list_columns,
self::$list_number,
$actions,
$this
);
if ( null !== $filter ) {
$actions = $filter;
}
}
}
/**
* Render bulk edit checkbox
*
* @param array $item Column list.
*
* @return string Content for checkbox column.
* @since 1.0.0
*/
public function column_cb( $item ) {
if ( !$this->bulk_actions_enabled ) {
// Bulk actions disabled.
return '';
}
if ( empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
// Table has no primary key: no bulk actions allowed!
// Primary key is used to ensure uniqueness!
return '';
}
// Build CB value: Use json format for multi column primary keys.
$cb_value = (object) null;
foreach ( $this->wpda_list_columns->get_table_primary_key() as $primary_key_column ) {
// JSON string key and values will be double quoted. Therefore a slash must be added to double quotes in values.
$cb_value->{$primary_key_column} = esc_attr( str_replace( '"', '\\"', $item[$primary_key_column] ) );
}
return "<input type='checkbox' name='bulk-selected[]' value='" . wp_json_encode( $cb_value ) . "' />";
}
/**
* Show page title
*
* @return void
*/
protected function show_title() {
if ( self::LIST_BASE_TABLE !== $this->table_name && (substr( $this->page, 0, 13 ) === \WP_Data_Access_Admin::PAGE_EXPLORER || \WP_Data_Access_Admin::PAGE_MAIN === $this->page) ) {
$this->subtitle = $this->title;
$this->title = 'Data Explorer';
}
echo esc_attr( $this->title );
}
/**
* Show page sub title
*
* @return void
*/
protected function show_sub_title() {
?>
<div class="wpda_subtitle"><strong><?php
echo wp_kses( $this->subtitle, array(
'span' => array(
'class' => array(),
),
) );
?></strong></div>
<?php
}
/**
* Show list table page
*
* Inside the list table page, the list table is shown. The necessary functionality to show the list table
* specifically is found in method {@see WPDA_List_Table::display()}.
*
* @since 1.0.0
*
* @see WPDA_List_Table::display()
*/
public function show() {
// Check for import requested.
if ( null !== $this->wpda_import ) {
$this->wpda_import->check_post();
}
// Prepare list table items.
$this->prepare_items();
// Show list table.
?>
<div class="wrap<?php
echo ( is_admin() ? '' : ' wp-core-ui' );
?>">
<?php
if ( $this->show_page_title ) {
?>
<h1 class="wp-heading-inline">
<?php
if ( self::LIST_BASE_TABLE !== $this->table_name && (current_user_can( 'manage_options' ) && \WP_Data_Access_Admin::PAGE_MAIN === $this->page) ) {
?>
<a
href="?page=<?php
echo esc_attr( $this->page );
?>"
class="dashicons dashicons-arrow-left-alt2 wpda_tooltip"
title="Data Explorer"
></a>
<?php
}
$this->show_title();
?>
</h1>
<?php
}
?>
<?php
if ( !is_admin() || 'wpda_wpdp_' === substr( $this->page, 0, 10 ) || WPDP::PAGE_TEMPLATES === $this->page || substr( $this->page, 0, 13 ) === \WP_Data_Access_Admin::PAGE_EXPLORER ) {
$this->add_header_button();
}
$this->add_header_actions();
if ( 'diehard' === $this->page ) {
$action_url = admin_url( 'admin-ajax.php' );
} else {
$action_url = admin_url( 'admin.php' );
}
?>
<div id="wpda_invisible_container" style="display:none;">
<form id="wpda_main_export_form" method="post" action="<?php
echo esc_url( $action_url );
?>?action=wpda_export" target="_blank">
<input type="text" name="type" id="wpda_main_export_form__type" value="table" />
<input type="text" name="wpdaschema_name" id="wpda_main_export_form__schema_name" />
<input type="text" name="_wpnonce" id="wpda_main_export_form__wpnonce" />
</form>
<form id="wpda_table_export_form" method="post" action="<?php
echo esc_url( $action_url );
?>?action=wpda_export" target="_blank">
<input type="text" name="type" id="wpda_table_export_form__type" value="table" />
<input type="text" name="wpdaschema_name" id="wpda_table_export_form__schema_name" />
<input type="text" name="table_names" id="wpda_table_export_form__table_names" />
<input type="text" name="format_type" id="wpda_table_export_form__format_type" />
<input type="text" name="include_table_settings" id="wpda_table_export_form__include_table_settings" />
<input type="text" name="_wpnonce" id="wpda_table_export_form__wpnonce" />
</form>
<form id="wpda_row_export_form" method="post" action="<?php
echo esc_url( $action_url );
?>?action=wpda_export" target="_blank">
<input type="text" name="type" id="wpda_row_export_form__type" value="row" />
<input type="text" name="pid" id="wpda_row_export_form__pid" />
<input type="text" name="wpdaschema_name" id="wpda_row_export_form__schema_name" />
<input type="text" name="table_names" id="wpda_row_export_form__table_names" />
<input type="text" name="mysql_set" id="wpda_row_export_form__mysql_set" />
<input type="text" name="show_create" id="wpda_row_export_form__show_create" />
<input type="text" name="show_comments" id="wpda_row_export_form__show_comments" />
<input type="text" name="format_type" id="wpda_row_export_form__format_type" />
<input type="text" name="_wpnonce" id="wpda_row_export_form__wpnonce" />
</form>
</div>
<?php
// Add import container.
if ( null !== $this->wpda_import ) {
$this->wpda_import->add_container();
}
// Add custom code before the list table.
do_action_ref_array( 'wpda_before_list_table', array($this) );
// Prepare url.
if ( is_admin() ) {
$url = "?page={$this->page}";
} else {
$url = '';
}
?>
<form id="wpda_main_form"
method="post"
action="<?php
echo esc_attr( $url );
?>"
style="
<?php
if ( '' !== $this->subtitle ) {
echo 'margin-top: 10px';
}
?>
"
>
<?php
$this->show_sub_title();
if ( $this->search_box_enabled ) {
$this->search_box( __( 'search', 'wp-data-access' ), 'search_id' );
}
$this->display();
if ( '' === $this->get_bulk_actions() ) {
// Add action item containing value -1. This will allow sorting if no bulk action listbox is displayed.
?>
<input type="hidden" name="action" value="-1"/>
<?php
}
if ( self::LIST_BASE_TABLE !== $this->table_name ) {
?>
<input type="hidden" name="wpdaschema_name"
value="<?php
echo esc_attr( $this->schema_name );
// input var okay.
?>"/>
<input type="hidden" name="table_name"
value="<?php
echo esc_attr( $this->table_name );
// input var okay.
?>"/>
<?php
}
?>
<input id="wpda_main_form_orderby" type="hidden" name="orderby"
value="<?php
echo ( isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) ) : '' );
// input var okay.
?>"/>
<input id="wpda_main_form_order" type="hidden" name="order"
value="<?php
echo ( isset( $_REQUEST['order'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) : '' );
// input var okay.
?>"/>
<input id="wpda_main_form_post_mime_type" type="hidden" name="post_mime_type"
value="<?php
echo ( isset( $_REQUEST['post_mime_type'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['post_mime_type'] ) ) ) : '' );
// input var okay.
?>"/>
<input id="wpda_main_form_detached" type="hidden" name="detached"
value="<?php
echo ( isset( $_REQUEST['detached'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['detached'] ) ) ) : '' );
// input var okay.
?>"/>
<input id="wpda_main_db_schema" type="hidden" name="wpda_main_db_schema"
value="<?php
echo ( isset( $_REQUEST['wpda_main_db_schema'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['wpda_main_db_schema'] ) ) ) : '' );
// input var okay.
?>"/>
<input id="wpda_main_favourites" type="hidden" name="wpda_main_favourites"
value="<?php
echo ( isset( $_REQUEST['wpda_main_favourites'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['wpda_main_favourites'] ) ) ) : '' );
// input var okay.
?>"/>
<?php
wp_nonce_field( 'wpda-export-' . wp_json_encode( $this->table_name ), '_wpnonce', false );
?>
<?php
wp_nonce_field( "wpda-delete-{$this->table_name}", '_wpnonce2', false );
?>
<?php
if ( self::LIST_BASE_TABLE === $this->table_name ) {
wp_nonce_field( 'wpda-drop-' . WPDA::get_current_user_login(), '_wpnonce3', false );
wp_nonce_field( 'wpda-truncate-' . WPDA::get_current_user_login(), '_wpnonce4', false );
}
?>
<?php
foreach ( $_REQUEST as $key => $value ) {
if ( substr( $key, 0, 19 ) === 'wpda_search_column_' ) {
if ( is_array( $value ) ) {
foreach ( $value as $elem_key => $elem_value ) {
echo '<input type="hidden" name="' . esc_attr( $key ) . '[]" value="' . esc_attr( $elem_value ) . '"/>';
}
} else {
echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $value ) . '"/>';
}
}
}
?>
</form>
</div>
<script type='text/javascript'>
jQuery(function() {
jQuery( '.wpda_tooltip' ).tooltip();
// Add toolbar events
jQuery("#wpda_toolbar_icon_go_backup").on("click", function() {
jQuery("#wpda_goto_backup").submit();
});
jQuery("#wpda_toolbar_icon_add_row").on("click", function() {
jQuery("#wpda_new_row_table_name").val("<?php
echo esc_attr( $this->table_name );
?>");
jQuery("#wpda_new_row").submit();
});
jQuery("#wpda_toolbar_icon_add_publication").on("click", function() {
jQuery("#wpda_new_publication_table_name").val("<?php
echo esc_attr( $this->table_name );
?>");
jQuery("#wpda_new_publication").submit();
});
});
</script>
<?php
$this->bind_action_buttons();
?>
<?php
// Add custom code after the list table.
do_action_ref_array( 'wpda_after_list_table', array($this) );
}
/**
* Placeholder for subclasses to add additional actions
*
* @return void
*/
protected function add_header_actions() {
}
/**
* Bind javascript code to action buttons
*
* @since 1.6.2
*/
protected function bind_action_buttons() {
?>
<script type='text/javascript'>
jQuery(function () {
jQuery("#doaction").click(function () {
return wpda_action_button();
});
jQuery("#doaction2").click(function () {
return wpda_action_button();
});
});
</script>
<?php
}
/**
* Add button to page header
*
* By default "add new" and "import" buttons are added (depending on the settings). Overwrite this method to
* add your own buttons.
*
* @since 1.0.1
*/
protected function add_header_button() {
if ( 'off' === $this->allow_insert ) {
if ( null !== $this->wpda_import ) {
$this->wpda_import->add_button();
}
} else {
//phpcs:ignore - 8.1 proof
if ( WPDA::is_wpda_table( $this->table_name ) || ('on' === WPDA::get_option( WPDA::OPTION_BE_ALLOW_INSERT ) && count( $this->wpda_list_columns->get_table_primary_key() )) > 0 ) {
$storage_type = ( WPDA::is_wpda_table( $this->table_name ) ? __( 'respository', 'wp-data-access' ) : __( 'table', 'wp-data-access' ) );
// Prepare url.
if ( is_admin() ) {
$url = "?page={$this->page}";
} else {
$url = '';
}
?>
<form
method="post"
action="<?php
echo esc_attr( $url );
?>"
style="display: inline-block; vertical-align: baseline;"
>
<div>
<input type="hidden" name="wpdaschema_name"
value="<?php
echo esc_attr( $this->schema_name );
// input var okay.
?>"/>
<input type="hidden" name="table_name"
value="<?php
echo esc_attr( $this->table_name );
// input var okay.
?>"/>
<input type="hidden" name="action" value="new">
<button type="submit" class="page-title-action wpda_tooltip"
title="<?php
echo sprintf( __( 'Add new %1$s to %2$s', 'wp-data-access' ), esc_attr( $this->_args['singular'] ), esc_attr( $storage_type ) );
?>"
>
<i class="fas fa-plus-circle wpda_icon_on_button"></i>
<?php
echo __( 'Add New', 'wp-data-access' );
?>
</button>
<?php
// Add import button to title.
if ( null !== $this->wpda_import ) {
$this->wpda_import->add_button();
}
?>
</div>
</form>
<?php
} else {
// Add import button to title.
if ( null !== $this->wpda_import ) {
$this->wpda_import->add_button();
}
}
}
}
/**
* Prepares the list of items for displaying
*
* Overwrites WP_List_Table::prepare_items()
*
* @since 1.0.0
*/
public function prepare_items() {
// Construct where clause with search values provided in the search box.
// Result (where clause) is written to $this->where.
$this->construct_where_clause();
$this->process_bulk_action();
if ( is_admin() ) {
if ( $this->table_name === self::LIST_BASE_TABLE ) {
$option = 'wpda_rows_per_page_' . str_replace( '.', '_', self::LIST_BASE_TABLE );
} else {
$option = 'wpda_rows_per_page_' . str_replace( '.', '_', $this->schema_name . $this->table_name );
}
$this->items_per_page = $this->get_items_per_page( $option, WPDA::get_option( WPDA::OPTION_BE_PAGINATION ) );
} else {
$this->items_per_page = WPDA::get_option( WPDA::OPTION_FE_PAGINATION );
}
$this->current_page = $this->get_pagenum();
$total_items = $this->record_count();
$total_pages = ceil( $total_items / $this->items_per_page );
if ( $this->search_value !== $this->search_value_old ) {
$this->current_page = 1;
}
if ( $this->current_page > $total_pages ) {
$this->current_page = $total_pages;
}
$this->set_pagination_args( array(
'total_items' => $total_items,
'total_pages' => $total_pages,
'per_page' => $this->items_per_page,
) );
$this->get_rows();
// Written to $this->items in base class (WP_List_Table).
$columns = $this->get_columns();
$hidden = $this->get_hidden_columns();
if ( false === $hidden ) {
if ( is_admin() ) {
// List table in backend.
if ( self::LIST_BASE_TABLE === $this->table_name ) {
$table_name = str_replace( '.', '_', self::LIST_BASE_TABLE );
} else {
global $wpdb;
if ( $this->schema_name === $wpdb->dbname && WPDA_CSV_Uploads_Model::get_base_table_name() === $this->table_name ) {
$table_name = $this->table_name;
// csv upload = exception.
} else {
$table_name = str_replace( '.', '_', $this->schema_name . $this->table_name );
}
}
$hidden = get_user_option( WPDA_List_View::HIDDENCOLUMNS_PREFIX . get_current_screen()->id . $table_name );
if ( false === $hidden ) {
$hidden = array();
}
}
}
if ( !is_array( $hidden ) ) {
$hidden = array();
}
$sortable = $this->get_sortable_columns();
$primary = $this->get_primary_column();
$this->_column_headers = array(
$columns,
$hidden,
$sortable,
$primary
);
}
/**
* Build where clause
*
* Arguments might come from the URL so we need to check for SQL injection and use prepare. The resulting
* where clause is written directly to member $this->where.
*
* @since 1.0.0
*/
protected function construct_where_clause() {
$whereclause = $this->get_where_clause();
if ( '' !== $whereclause ) {
$this->where = ( '' === $this->where ? " where ({$whereclause}) " : " {$this->where} and ({$whereclause}) " );
}
}
/**
* Returns the where clause (uses filters if available)
*
* @return string
*/
public function get_where_clause() {
return WPDA::construct_where_clause(
$this->schema_name,
$this->table_name,
$this->wpda_list_columns->get_searchable_table_columns(),
$this->search_value,
isset( $_REQUEST['wpda_c'] ) && 'true' === $_REQUEST['wpda_c']
);
}
/**
* Process bulk actions
*
* Delete and bulk-delete actions use the primary key list as a reference. Before performing the actual delete
* statement(s) we'll check the provided column names against the data dictionary. This will protect us against
* SQL injection attacks.
*
* For export actions table names will not be checked here. They are handed over WPDA_Export where the validity
* and access rights are checked anyway (to be safe).
*
* All actions can be processed only with a valid wpnonce value.
*
* To perform actions the user must have the appropriate rights.
*
* @since 1.0.0
*/
public function process_bulk_action() {
switch ( $this->current_action() ) {
case 'delete':
// Check access rights.
if ( 'on' !== $this->allow_delete ) {
// Deleting records from list table is not allowed.
wp_die( __( 'ERROR: Not authorized [delete not allowed]', 'wp-data-access' ) );
}
// Prepare wp_nonce action security check.
$wp_nonce_action = "wpda-delete-{$this->table_name}";
$row_to_be_deleted = array();
// Gonna hold the row to be deleted.
$i = 0;
// Index, necessary for multi column keys.
// Check all key columns.
foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
// Check if key is available.
if ( !isset( $_REQUEST[$key] ) ) {
// input var okay.
wp_die( __( 'ERROR: Invalid URL [missing primary key values]', 'wp-data-access' ) );
}
// Write key value pair to array.
$row_to_be_deleted[$i]['key'] = $key;
$row_to_be_deleted[$i]['value'] = sanitize_text_field( wp_unslash( $_REQUEST[$key] ) );
// input var okay.
$i++;
// Add key values to wp_nonce action.
$wp_nonce_action .= '-' . sanitize_text_field( wp_unslash( $_REQUEST[$key] ) );
// input var okay.
}
// Check if delete is allowed.
$wp_nonce = ( isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '' );
// input var okay.
if ( !wp_verify_nonce( $wp_nonce, $wp_nonce_action ) ) {
wp_die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
}
// All key column values available: delete record.
// Prepare named array for delete operation.
$next_row_to_be_deleted = array();
$count_rows = count( $row_to_be_deleted );
//phpcs:ignore - 8.1 proof
for ($i = 0; $i < $count_rows; $i++) {
$next_row_to_be_deleted[$row_to_be_deleted[$i]['key']] = $row_to_be_deleted[$i]['value'];
}
$engine = WPDA::get_table_engine( $this->schema_name, $this->table_name );
if ( $this->delete_row( $next_row_to_be_deleted, $engine ) ) {
$msg = new WPDA_Message_Box(array(
'message_text' => __( 'Row deleted', 'wp-data-access' ),
));
$msg->box();
} else {
$msg = new WPDA_Message_Box(array(
'message_text' => __( 'Could not delete row', 'wp-data-access' ),
'message_type' => 'error',
'message_is_dismissible' => false,
));
$msg->box();
}
break;
case 'bulk-delete':
// Check access rights.
if ( 'on' !== $this->allow_delete ) {
// Deleting records from list table is not allowed.
die( __( 'ERROR: Not authorized [delete not allowed]', 'wp-data-access' ) );
}
// We first need to check if all the necessary information is available.
if ( !isset( $_REQUEST['bulk-selected'] ) ) {
// input var okay.
// Nothing to delete.
$msg = new WPDA_Message_Box(array(
'message_text' => __( 'Nothing to delete', 'wp-data-access' ),
));
$msg->box();
return;
}
// Check if delete is allowed.
$wp_nonce_action = "wpda-delete-{$this->table_name}";
$wp_nonce = ( isset( $_REQUEST['_wpnonce2'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce2'] ) ) : '' );
// input var okay.
if ( !wp_verify_nonce( $wp_nonce, $wp_nonce_action ) ) {
die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
}
$bulk_rows = (array) $_REQUEST['bulk-selected'];
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$no_rows = count( $bulk_rows );
// # rows to be deleted. //phpcs:ignore - 8.1 proof being safe
$rows_to_be_deleted = array();
// Gonna hold rows to be deleted.
for ($i = 0; $i < $no_rows; $i++) {
// Write "json" to named array. Need to strip slashes twice. Once for the normal conversion
// and once extra for the pre-conversion of double quotes in method column_cb().
$row_object = json_decode( stripslashes( stripslashes( $bulk_rows[$i] ) ), true );
if ( $row_object ) {
$j = 0;
// Index used to build array.
// Check all key columns.
foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
// Check if key is available.
if ( !isset( $row_object[$key] ) ) {
wp_die( __( 'ERROR: Invalid URL [missing primary key values]', 'wp-data-access' ) );
}
// Write key value pair to array.
$rows_to_be_deleted[$i][$j]['key'] = $key;
$rows_to_be_deleted[$i][$j]['value'] = $row_object[$key];
$j++;
}
}
}
// Looks like everything is there. Delete records from table...
$no_key_cols = count( $this->wpda_list_columns->get_table_primary_key() );
//phpcs:ignore - 8.1 proof
$rows_successfully_deleted = 0;
// Number of rows successfully deleted.
$rows_with_errors = 0;
// Number of rows that could not be deleted.
$engine = WPDA::get_table_engine( $this->schema_name, $this->table_name );
for ($i = 0; $i < $no_rows; $i++) {
// Prepare named array for delete operation.
$next_row_to_be_deleted = array();
$row_found = true;
for ($j = 0; $j < $no_key_cols; $j++) {
if ( isset( $rows_to_be_deleted[$i][$j]['key'] ) ) {
$next_row_to_be_deleted[$rows_to_be_deleted[$i][$j]['key']] = $rows_to_be_deleted[$i][$j]['value'];
} else {
$row_found = false;
}
}
if ( $row_found ) {
if ( $this->delete_row( $next_row_to_be_deleted, $engine ) ) {
// Row(s) successfully deleted.
$rows_successfully_deleted++;
} else {
// An error occurred during the delete operation: increase error count.
$rows_with_errors++;
}
} else {
// An error occurred during the delete operation: increase error count.
$rows_with_errors++;
}
}
// Inform user about the results of the operation.
$message = '';
if ( 1 === $rows_successfully_deleted ) {
$message = __( 'Row deleted', 'wp-data-access' );
} elseif ( $rows_successfully_deleted > 1 ) {
$message = "{$rows_successfully_deleted} " . __( 'rows deleted', 'wp-data-access' );
}
if ( '' !== $message ) {
$msg = new WPDA_Message_Box(array(
'message_text' => $message,
));
$msg->box();
}
$message = '';
if ( $rows_with_errors > 0 ) {
$message = __( 'Not all rows have been deleted', 'wp-data-access' );
}
if ( '' !== $message ) {
$msg = new WPDA_Message_Box(array(
'message_text' => $message,
'message_type' => 'error',
'message_is_dismissible' => false,
));
$msg->box();
}
break;
case 'bulk-export':
case 'bulk-export-xml':
case 'bulk-export-json':
case 'bulk-export-excel':
case 'bulk-export-csv':
// Check access rights.
if ( !WPDA::is_wpda_table( $this->table_name ) ) {
if ( 'on' !== WPDA::get_option( WPDA::OPTION_BE_EXPORT_ROWS ) ) {
// Exporting rows from list table is not allowed.
die( __( 'ERROR: Not authorized [export not allowed]', 'wp-data-access' ) );
}
}
// We first need to check if all the necessary information is available.
if ( !isset( $_REQUEST['bulk-selected'] ) ) {
// input var okay.
// Nothing to export.
$msg = new WPDA_Message_Box(array(
'message_text' => __( 'Nothing to export', 'wp-data-access' ),
));
$msg->box();
return;
}
// Check if export is allowed.
$wp_nonce = ( isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '' );
// input var okay.
if ( !wp_verify_nonce( $wp_nonce, 'wpda-export-' . wp_json_encode( $this->table_name ) ) ) {
die( __( 'ERROR: Not authorized', 'wp-data-access' ) );
}
$bulk_rows = (array) $_REQUEST['bulk-selected'];
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$no_rows = count( $bulk_rows );
// # rows to be exported. //phpcs:ignore - 8.1 proof being safe
$format_type = '';
switch ( $this->current_action() ) {
case 'bulk-export-xml':
$format_type = 'xml';
break;
case 'bulk-export-json':
$format_type = 'json';
break;
case 'bulk-export-excel':
$format_type = 'excel';
break;
case 'bulk-export-csv':
$format_type = 'csv';
}
if ( 'diehard' === $this->page && null !== $this->pid ) {
$wp_nonce = wp_create_nonce( "wpda-export-{$this->pid}" );
$submit_schema_name = '';
$submit_table_name = '';
} else {
$submit_schema_name = $this->schema_name;
$submit_table_name = $this->table_name;
}
$j = 0;
$columns = array();
for ($i = 0; $i < $no_rows; $i++) {
// Write "json" to named array. Need to strip slashes twice. Once for the normal conversion
// and once extra for the pre-conversion of double quotes in method column_cb().
$row_object = json_decode( stripslashes( stripslashes( $bulk_rows[$i] ) ), true );
if ( $row_object ) {
// Check all key columns.
foreach ( $this->wpda_list_columns->get_table_primary_key() as $key ) {
// Check if key is available.
if ( !isset( $row_object[$key] ) ) {
wp_die( __( 'ERROR: Invalid URL', 'wp-data-access' ) );
}
if ( !isset( $columns[$key] ) ) {
$columns[$key] = array();
}
$columns[$key][] = $row_object[$key];
}
$j++;
}
}
?>
<script type="application/javascript">
jQuery(function() {
wpda_row_export(
'<?php
echo esc_attr( $this->pid );
?>',
'<?php
echo esc_attr( $submit_schema_name );
?>',
'<?php
echo esc_attr( $submit_table_name );
?>',
'<?php
echo esc_attr( $wp_nonce );
?>',
'<?php
echo esc_attr( $format_type );
?>',
'off',
'off',
'off',
'<?php
echo wp_json_encode( $columns );
?>'
);
});
</script>
<?php
}
}
/**
* Delete record from database table
*
* The table must have a primary key and the values for all primary key columns must be provided in the
* request. The where clause must be a named array in format: ['column_name'] = 'value'
*
* @param string $where where clause.
* @param string $engine connect or empty.
*
* @return mixed
* @since 1.0.0
*/
public function delete_row( $where, $engine = '' ) {
$wpdadb = WPDADB::get_db_connection( $this->schema_name );
if ( null === $wpdadb ) {
if ( is_admin() ) {
wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
} else {
die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
}
}
$row_deleted = $wpdadb->delete( $this->table_name, $where );
if ( 'connect' === strtolower( $engine ) ) {
// Connect engine does not return number of rows deleted.
// Presuming delete was successful when no error message was returned.
return '' === $wpdadb->last_error;
}
return $row_deleted;
}
/**
* Number of records in database table
*
* Returns the number of records in the database table. Where clause must be prepared in advance and written
* to $this->where ({@see WPDA_List_Table::construct_where_clause()})
*
* @return null|string Number of rows in the current table
* @since 1.0.0
*/
public function record_count() {
if ( !$this->row_count_estimate['do_real_count'] && '' === $this->where ) {
// Use estimate row count.
return $this->table_rows;
}
// Get real row count.
$wpdadb = WPDADB::get_db_connection( $this->schema_name );
if ( null === $wpdadb ) {
if ( is_admin() ) {
wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
} else {
die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
}
}
if ( '' === $this->schema_name ) {
$query = "\n\t\t\t\t\tselect count(*)\n\t\t\t\t\tfrom `{$this->table_name}`\n\t\t\t\t\t{$this->where}\n\t\t\t\t";
} else {
if ( self::LIST_BASE_TABLE === $this->table_name ) {
$query = "\n\t\t\t\t\t\tselect count(*)\n\t\t\t\t\t\tfrom {$this->table_name}\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
} else {
$query = "\n\t\t\t\t\t\tselect count(*)\n\t\t\t\t\t\tfrom `{$wpdadb->dbname}`.`{$this->table_name}`\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
}
}
return $wpdadb->get_var( $query );
// phpcs:ignore Standard.Category.SniffName.ErrorCode
}
/**
* Perform query to retrieve rows from database
*
* Where clause must be prepared in advance and written to $this->where
* ({@see WPDA_List_Table::construct_where_clause()}).
*
* No return value. Result is directly written to $this->items (base class member).
*
* @since 1.0.0
*/
public function get_rows() {
$wpdadb = WPDADB::get_db_connection( $this->schema_name );
if ( null === $wpdadb ) {
if ( is_admin() ) {
wp_die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
} else {
die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
}
}
// Selected columns cannot be changed by the user at this time. No check for SQL injection needed now.
// This might change in the future when users are allowed to change or set this value. A method named
// column_exists() in WPDA_Dictionary_Checks is already available for this purpose.
$selected_columns = '*';
if ( isset( $this->columns_queried ) ) {
$selected_columns = implode( ',', $this->columns_queried );
}
if ( isset( $this->wpda_table_settings->hyperlinks ) ) {
$i = 0;
foreach ( $this->wpda_table_settings->hyperlinks as $hyperlink ) {
$skip_column = false;
if ( null !== $this->wpda_project_table_settings ) {
$hyperlink_label = $hyperlink->hyperlink_label;
if ( !property_exists( $this, 'is_child' ) ) {
if ( isset( $this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) {
$skip_column = true;
}
} else {
if ( isset( $this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) {
$skip_column = true;
}
}
}
if ( !$skip_column && isset( $hyperlink->hyperlink_list ) && true === $hyperlink->hyperlink_list ) {
$hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
$hyperlink_html = ( isset( $hyperlink->hyperlink_html ) ? $hyperlink->hyperlink_html : '' );
if ( '' !== $hyperlink_label && '' !== $hyperlink_html ) {
// Add hyperlink columns (details are added in method column_default).
$selected_columns .= ", '' as wpda_hyperlink_{$i}";
}
}
$i++;
}
}
if ( '' === $this->schema_name ) {
$query = "\n\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\tfrom `{$this->table_name}`\n\t\t\t\t\t{$this->where}\n\t\t\t\t";
} else {
if ( self::LIST_BASE_TABLE === $this->table_name ) {
$query = "\n\t\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\t\tfrom {$this->table_name}\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
} else {
$query = "\n\t\t\t\t\t\tselect {$selected_columns}\n\t\t\t\t\t\tfrom `{$wpdadb->dbname}`.`{$this->table_name}`\n\t\t\t\t\t\t{$this->where}\n\t\t\t\t\t";
}
}
$query .= $this->get_order_by();
// Add order by.
$query .= " limit {$this->items_per_page}";
$offset = ($this->current_page - 1) * $this->items_per_page;
if ( $offset > 0 ) {
$query .= " offset {$offset}";
}
// Debug query.
// var_dump( $query );
$this->items = $wpdadb->get_results( $query, 'ARRAY_A' );
// phpcs:ignore Standard.Category.SniffName.ErrorCode
}
/**
* Get order by
*
* @return string|void
*/
protected function get_order_by() {
if ( !empty( $_REQUEST['orderby'] ) ) {
$orderby_arg = sanitize_sql_orderby( wp_unslash( $_REQUEST['orderby'] ) );
// input var okay.
if ( !empty( $_REQUEST['order'] ) ) {
$order_arg = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) );
// input var okay.
} else {
$order_arg = '';
}
$columns = $this->get_sortable_columns();
// Check column name for SQL injection.
if ( isset( $columns[$orderby_arg] ) || $this->wpda_data_dictionary->column_exists( $orderby_arg ) ) {
// Column name exists in current table, safely continue...
$orderby = " order by {$orderby_arg}";
// Prevent SQL injection for order. If 'desc' is found result will be ordered desc. In all other
// cases we'll order asc.
$orderby .= ( strtolower( trim( $order_arg ) ) === 'desc' ? ' desc' : ' asc' );
return $orderby;
} else {
// The user provided a column name which is not in the table. Most probably the result of a
// SQL injection attack, so let's terminate.
wp_die( __( 'ERROR: Invalid URL [invalid column name]', 'wp-data-access' ) );
}
} else {
return '';
}
}
/**
* Return an associative array of columns
*
* @return array
* @since 1.0.0
*/
public function get_columns() {
if ( null !== $this->wpda_cached_columns && is_array( $this->wpda_cached_columns ) ) {
return $this->wpda_cached_columns;
}
$columns = array();
if ( $this->bulk_actions_enabled ) {
if ( !empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
// Tables has primary key: bulk actions allowed!
// Primary key is used to ensure uniqueness.
$actions = $this->get_bulk_actions();
if ( is_array( $actions ) && 0 < count( $actions ) ) {
//phpcs:ignore - 8.1 proof
$columns = array(
'cb' => '<input type="checkbox" />',
);
}
}
}
$columnlist = $this->wpda_list_columns->get_table_column_headers();
foreach ( $columnlist as $key => $value ) {
$columns[$key] = $value;
// Check for alternative column header.
if ( isset( $this->column_headers[$key] ) ) {
// Alternative header found: use it.
$columns[$key] = $this->column_headers[$key];
} else {
// Default behaviour: get column header from generated label.
$columns[$key] = $this->wpda_list_columns->get_column_label( $key );
}
}
if ( isset( $this->wpda_table_settings->hyperlinks ) ) {
$i = 0;
foreach ( $this->wpda_table_settings->hyperlinks as $hyperlink ) {
if ( isset( $hyperlink->hyperlink_list ) && true === $hyperlink->hyperlink_list ) {
$skip_column = false;
if ( null !== $this->wpda_project_table_settings ) {
$hyperlink_label = $hyperlink->hyperlink_label;
if ( !property_exists( $this, 'is_child' ) ) {
if ( isset( $this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_parent->{$hyperlink_label} ) {
$skip_column = true;
}
} else {
if ( isset( $this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) && !$this->wpda_project_table_settings->hyperlinks_child->{$hyperlink_label} ) {
$skip_column = true;
}
}
}
if ( !$skip_column ) {
$hyperlink_label = ( isset( $hyperlink->hyperlink_label ) ? $hyperlink->hyperlink_label : '' );
$columns["wpda_hyperlink_{$i}"] = $hyperlink_label;
// Add hyperlink label.
}
}
$i++;
}
}
// Cache columns.
$this->wpda_cached_columns = $columns;
return $columns;
}
/**
* List of columns to make sortable
*
* @return array
* @since 1.0.0
*/
public function get_sortable_columns() {
$columns = array();
// Get column names from result set.
if ( $this->items ) {
foreach ( $this->items[0] as $key => $value ) {
if ( 'wpda_hyperlink_' !== substr( $key, 0, 15 ) ) {
$columns[$key] = array($key, false);
}
}
}
return $columns;
}
/**
* Display the search box
*
* @param string $text The 'submit' button label.
* @param string $input_id ID attribute value for the search input field.
*
* @since 1.0.0
*/
public function search_box( $text, $input_id ) {
$input_id = $input_id . '-search-input';
// Allow external user to add search actions (like icons) to the search box.
do_action(
'wpda_add_search_actions',
$this->schema_name,
$this->table_name,
$this->wpda_table_settings,
$this->wpda_list_columns
);
?>
<p class="search-box" <?php
echo ( !$this->has_items() ? 'style="padding-bottom:10px;"' : '' );
?>>
<input type="search" id="<?php
echo esc_attr( $input_id );
?>"
name="<?php
echo esc_attr( $this->search_item_name );
?>"
value="<?php
echo esc_attr( $this->search_value );
?>"/>
<?php
if ( is_admin() ) {
submit_button(
$text,
'',
'',
false,
array(
'id' => 'search-submit',
)
);
} else {
wpdadiehard_submit_button(
$text,
'',
'',
false,
array(
'id' => 'search-submit',
)
);
}
?>
<input type="hidden" name="<?php
echo esc_attr( $this->search_item_name );
?>_old_value"
value="<?php
echo esc_attr( $this->search_value );
?>"/>
</p>
<?php
// Allow external user to add search html (like extra items) below the search box.
do_action(
'wpda_add_search_filter',
$this->schema_name,
$this->table_name,
$this->wpda_table_settings,
$this->wpda_list_columns
);
}
/**
* Get search value (entered by the user or taken from cookie).
*
* @return string
* @since 1.5.0
*/
protected function get_search_value() {
if ( 'off' === WPDA::get_option( WPDA::OPTION_BE_REMEMBER_SEARCH ) ) {
if ( isset( $_REQUEST[$this->search_item_name] ) ) {
return wp_filter_nohtml_kses( wp_unslash( $_REQUEST[$this->search_item_name] ) );
// input var okay.
}
}
if ( 'wpda_wpdp_' === substr( $this->page, 0, 10 ) ) {
$cookie_name = $this->page;
if ( isset( $_REQUEST['child_request'] ) ) {
$cookie_name = 'NOCOOKIESFORCHILDREQUESTS';
// No search values stored for child tables.
}
foreach ( $_REQUEST as $key => $val ) {
if ( strpos( $key, 'WPDA_PARENT_KEY*' ) === 0 ) {
$cookie_name = 'NOCOOKIESFORCHILDREQUESTS';
// No search values stored for child tables.
}
}
} else {
$cookie_name = $this->page . '_search_' . str_replace( '.', '_', $this->table_name );
}
if ( isset( $_REQUEST[$this->search_item_name] ) && '' !== $_REQUEST[$this->search_item_name] ) {
// input var okay.
return wp_filter_nohtml_kses( wp_unslash( $_REQUEST[$this->search_item_name] ) );
// input var okay.
} elseif ( isset( $_COOKIE[$cookie_name] ) ) {
return wp_filter_nohtml_kses( wp_unslash( $_COOKIE[$cookie_name] ) );
// input var okay.
} else {
return null;
}
}
/**
* Print column headers
*
* Overriding original method print_column_headers to support post instead of get.
* Changes are marked!
*
* @param boolean $with_id Whether to set the id attribute or not.
*
* @since 1.0.0
*
* @staticvar $cb_counter int
*/
public function print_column_headers( $with_id = true ) {
list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
//phpcs:ignore - 8.1 proof
// *********************
// *** BEGIN CHANGES ***
// *********************
// Code removed.
// *******************
// *** END CHANGES ***
// *******************
if ( isset( $_REQUEST['orderby'] ) ) {
$current_orderby = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) );
// input var okay.
} else {
$current_orderby = '';
}
if ( isset( $_REQUEST['order'] ) && 'desc' === $_REQUEST['order'] ) {
// input var okay.
$current_order = 'desc';
} else {
$current_order = 'asc';
}
if ( !empty( $columns['cb'] ) ) {
static $cb_counter = 1;
$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . __( 'Select All' ) . '</label>' . '<input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
$cb_counter++;
}
foreach ( $columns as $column_key => $column_display_name ) {
$class = array('manage-column', "column-{$column_key}");
if ( in_array( $column_key, (array) $hidden ) ) {
//phpcs:ignore - 8.1 proof
$class[] = 'hidden';
}
if ( 'cb' === $column_key ) {
$class[] = 'check-column';
} elseif ( in_array( $column_key, array('posts', 'comments', 'links') ) ) {
$class[] = 'num';
}
if ( $column_key === $primary ) {
$class[] = 'column-primary';
}
if ( isset( $sortable[$column_key] ) ) {
list( $orderby, $desc_first ) = (array) $sortable[$column_key];
//phpcs:ignore - 8.1 proof
if ( $current_orderby === $orderby ) {
$order = ( 'asc' === $current_order ? 'desc' : 'asc' );
$class[] = 'sorted';
$class[] = $current_order;
} else {
$order = ( $desc_first ? 'desc' : 'asc' );
$class[] = 'sortable';
$class[] = ( $desc_first ? 'asc' : 'desc' );
}
// *********************
// *** BEGIN CHANGES ***
// *********************
// Code removed.
// *******************
// *** END CHANGES ***
// *******************
}
$tag = ( 'cb' === $column_key ? 'td' : 'th' );
$scope = ( 'th' === $tag ? 'scope="col"' : '' );
$id = ( $with_id ? "id='{$column_key}'" : '' );
if ( !empty( $class ) ) {
$class = "class='" . join( ' ', $class ) . "'";
}
// *********************
// *** BEGIN CHANGES ***
// *********************
echo '<' . esc_attr( $tag ) . ' ' . wp_kses_data( $scope ) . ' ' . wp_kses_data( $id ) . ' ' . wp_kses_data( $class ) . '>';
if ( isset( $sortable[$column_key] ) ) {
?>
<a href="javascript:void(0)"
onclick="jQuery('#wpda_main_form_orderby').val('<?php
echo esc_attr( $orderby );
?>'); jQuery('#wpda_main_form_order').val('<?php
echo esc_attr( $order );
?>'); jQuery('#wpda_main_form').submit();">
<span><?php
echo wp_kses_data( $column_display_name );
?></span><span
class="sorting-indicator"></span>
</a>
<?php
} else {
echo wp_kses( $column_display_name, array(
'input' => array(
'id' => array(),
'type' => array(),
),
'label' => array(
'class' => array(),
'for' => array(),
),
) );
}
echo '</' . esc_attr( $tag ) . '>';
// *******************
// *** END CHANGES ***
// *******************
}
}
/**
* Returns an associative array containing the bulk action
*
* @return array
* @since 1.0.0
*/
public function get_bulk_actions() {
if ( !$this->bulk_actions_enabled ) {
// Bulk actions disabled.
return '';
}
if ( empty( $this->wpda_list_columns->get_table_primary_key() ) ) {
// Tables has no primary key: no bulk actions allowed!
// Primary key is necessary to ensure uniqueness.
return '';
}
$actions = array();
if ( 'on' === $this->allow_delete ) {
$actions = array(
'bulk-delete' => __( 'Delete Permanently', 'wp-data-access' ),
);
}
if ( $this->bulk_export_enabled && (WPDA::is_wpda_table( $this->table_name ) || WPDA::get_option( WPDA::OPTION_BE_EXPORT_ROWS ) === 'on') ) {
if ( 'diehard' !== $this->page ) {
// Not allowed on web page.
$actions['bulk-export'] = __( 'Export to SQL', 'wp-data-access' );
}
$actions['bulk-export-xml'] = __( 'Export to XML', 'wp-data-access' );
$actions['bulk-export-json'] = __( 'Export to JSON', 'wp-data-access' );
$actions['bulk-export-excel'] = __( 'Export to Excel', 'wp-data-access' );
$actions['bulk-export-csv'] = __( 'Export to CSV', 'wp-data-access' );
}
return $actions;
}
/**
* Generates the table navigation
*
* Generates the table navigation above or bellow the table and removes the
* _wp_http_referrer and _wpnonce because it generates a error about URL too large.
*
* @param string $which CSS Class name.
*
* @return void
* @since 1.0.0
*/
protected function display_tablenav( $which ) {
if ( !$this->hide_navigation && $this->items ) {
?>
<div class="tablenav <?php
echo esc_attr( $which );
?>">
<div class="alignleft actions">
<?php
$this->bulk_actions( $which );
?>
</div>
<span id="wpda_row_export_anchor"></span>
<?php
$this->add_full_table_downloads();
$this->extra_tablenav( $which );
$this->pagination( $which );
?>
<br class="clear"/>
</div>
<?php
} else {
?>
<br class="clear"/>
<?php
}
}
/**
* Add full export to CSV and JSON buttons
* Needs to be granted in project page configuration
*/
protected function add_full_table_downloads() {
}
// Override to add arguments to CSV and JSON full table exports.
protected function add_full_table_downloads_add_args() {
}
/**
* Display the pagination
*
* Overriding original method pagination to support post instead of get.
* Changes are marked!
*
* @param string $which CSS Class name.
*
* @since 1.0.0
*/
protected function pagination( $which ) {
if ( empty( $this->_pagination_args ) ) {
return;
}
$total_items = $this->_pagination_args['total_items'];
$total_pages = $this->_pagination_args['total_pages'];
$infinite_scroll = false;
if ( isset( $this->_pagination_args['infinite_scroll'] ) ) {
$infinite_scroll = $this->_pagination_args['infinite_scroll'];
}
if ( 'top' === $which && $total_pages > 1 ) {
$this->screen->render_screen_reader_content( 'heading_pagination' );
}
// Add estimate character if row_count_estimate is enabled.
$estimate = ( $this->row_count_estimate['is_estimate'] ? '~' : '' );
/* translators: %s: number of items (2x) */
$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items ), $estimate . number_format_i18n( $total_items ) ) . '</span>';
$current = $this->get_pagenum();
if ( $this->search_value !== $this->search_value_old ) {
$current = 1;
}
// *********************
// *** BEGIN CHANGES ***
// *********************
// Code removed.
// *******************
// *** END CHANGES ***
// *******************
$page_links = array();
$total_pages_before = '<span class="paging-input">';
$total_pages_after = '</span></span>';
$disable_first = $disable_last = $disable_prev = $disable_next = false;
if ( 1 === (int) $current ) {
$disable_first = true;
$disable_prev = true;
}
if ( 2 === (int) $current ) {
$disable_first = true;
}
if ( (int) $current === (int) $total_pages ) {
$disable_last = true;
$disable_next = true;
}
if ( (int) $current === (int) $total_pages - 1 ) {
$disable_last = true;
}
// *********************
// *** BEGIN CHANGES ***
// *********************
$link_with_post_support = "\n <a class='%s'\n href='javascript:void(0)'\n onclick='jQuery(\"#current-page-selector\").val(\"%s\"); jQuery(\"#wpda_main_form\").submit();'>\n <span class='screen-reader-text'>%s</span>\n <span aria-hidden='true'>%s</span>\n </a>";
// *******************
// *** END CHANGES ***
// *******************
if ( $disable_first ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>';
} else {
// *********************
// *** BEGIN CHANGES ***
// *********************
$page_links[] = sprintf(
$link_with_post_support,
'first-page button',
'',
__( 'First page' ),
'«'
);
// *******************
// *** END CHANGES ***
// *******************
}
if ( $disable_prev ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>';
} else {
// *********************
// *** BEGIN CHANGES ***
// *********************
$page_links[] = sprintf(
$link_with_post_support,
'prev-page button',
max( 1, $current - 1 ),
__( 'Previous page' ),
'‹'
);
// *******************
// *** END CHANGES ***
// *******************
}
if ( 'bottom' === $which ) {
$html_current_page = $current;
$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page' ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
} else {
$html_current_page = sprintf(
"%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page' ) . '</label>',
$current,
strlen( (string) $total_pages )
);
}
$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
/* translators: %s: current page/total pages */
$page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
if ( $disable_next ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>';
} else {
// *********************
// *** BEGIN CHANGES ***
// *********************
$page_links[] = sprintf(
$link_with_post_support,
'next-page button',
min( $total_pages, $current + 1 ),
__( 'Next page' ),
'›'
);
// *******************
// *** END CHANGES ***
// *******************
}
if ( $disable_last ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">»</span>';
} else {
// *********************
// *** BEGIN CHANGES ***
// *********************
$page_links[] = sprintf(
$link_with_post_support,
'last-page button',
$total_pages,
__( 'Last page' ),
'»'
);
// *******************
// *** END CHANGES ***
// *******************
}
$pagination_links_class = 'pagination-links';
if ( !empty( $infinite_scroll ) ) {
$pagination_links_class = ' hide-if-js';
}
$output .= "\n<span class='{$pagination_links_class}'>" . join( "\n", $page_links ) . '</span>';
if ( $total_pages ) {
$page_class = ( $total_pages < 2 ? ' one-page' : '' );
} else {
$page_class = ' no-pages';
}
$this->_pagination = "<div class='tablenav-pages{$page_class}'>{$output}</div>";
echo $this->_pagination;
// phpcs:ignore WordPress.Security.EscapeOutput
}
}