Current File : /home/resuelf/www/wp-content/plugins/aawp/src/ClickTracking/ListTable.php |
<?php
namespace AAWP\ClickTracking;
use WP_List_Table;
use AAWP_DB_Products as Products;
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* Class ClickTracking ListTable.
*
* @since 3.20
*/
class ListTable extends WP_List_Table {
/**
* DB Class.
*
* @var \AAWP\ClickTracking\DB DB.
*/
private $db;
/**
* Products Class.
*
* @var AAWP_DB_Products
*/
private $products;
/**
* ListTable constructor.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\DB $db DB.
*/
public function __construct( $db ) {
$this->db = $db;
$this->products = new Products();
parent::__construct(
[
'plural' => esc_html__( 'Clicks', 'aawp' ),
'singular' => esc_html__( 'Click', 'aawp' ),
]
);
add_filter( 'default_hidden_columns', [ $this, 'default_hidden_columns' ], 10, 2 );
add_action( 'admin_init', [ $this, 'perform_actions' ] );
}
/**
* Prepare table list items.
*
* @global wpdb $wpdb
*/
public function prepare_items() {
if ( ! $this->db->is_click_tracking_enabled() ) {
return;
}
global $wpdb;
$this->prepare_column_headers();
$per_page = $this->get_items_per_page( 'aawp_click_items_per_page', 15 );
$where = $this->get_items_query_where();
$order = $this->get_items_query_order();
$limit = $this->get_items_query_limit();
$offset = $this->get_items_query_offset();
$query_items = "
SELECT * FROM {$wpdb->prefix}aawp_clicks
{$where} {$order} {$limit} {$offset}
";
// Count query needs to be different the total number varies due to timespan filter.
$count_query = "SELECT COUNT( id ) as count FROM {$wpdb->prefix}aawp_clicks {$where}";
$total_items = ! empty( $wpdb->get_results( $count_query )[0]->count ) ? $wpdb->get_results( $count_query )[0]->count : 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
$this->items = $wpdb->get_results( $query_items ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
$this->set_pagination_args(
[
'total_items' => $total_items,
'per_page' => $per_page,
'total_pages' => ceil( $total_items / $per_page ),
]
);
}
/**
* Get prepared LIMIT clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared LIMIT clause for items query.
*/
protected function get_items_query_limit() {
global $wpdb;
$per_page = $this->get_items_per_page( 'aawp_click_items_per_page', 15 );
return $wpdb->prepare( 'LIMIT %d', $per_page );
}
/**
* Get prepared OFFSET clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared OFFSET clause for items query.
*/
protected function get_items_query_offset() {
global $wpdb;
$per_page = $this->get_items_per_page( 'aawp_click_items_per_page', 15 );
$current_page = $this->get_pagenum();
if ( 1 < $current_page ) {
$offset = $per_page * ( $current_page - 1 );
} else {
$offset = 0;
}
return $wpdb->prepare( 'OFFSET %d', $offset );
}
/**
* Get prepared ORDER BY clause for items query
*
* @return string Prepared ORDER BY clause for items query.
*/
protected function get_items_query_order() {
$valid_orders = [ 'id', 'product', 'created_at' ];
if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], $valid_orders, true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$by = sanitize_sql_orderby( wp_unslash( $_REQUEST['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
$by = 'created_at';
}
$by = esc_sql( $by );
if ( ! empty( $_REQUEST['order'] ) && 'asc' === strtolower( $_REQUEST['order'] ) ) { // phpcs:ignore
$order = 'ASC';
} else {
$order = 'DESC';
}
return "ORDER BY {$by} {$order}, id {$order}";
}
/**
* Column id.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return int|string
*/
public function column_id( $item ) {
return absint( $item->id );
}
/**
* Column Asin.
*
* @param Object $item A listtable item.
*
* @since 3.20
*
* @return string
*/
public function column_asin( $item ) {
$product = $this->products->get_product_by( 'id', $item->product_id );
return ! empty( $product['asin'] ) ? $product['asin'] : '-';
}
/**
* Column Preview.
*
* @param Object $item A listtable item.
*
* @since 3.20
*
* @return string
*/
public function column_preview( $item ) {
$item = $this->products->get_product_by( 'id', $item->product_id );
$image_id = ! empty( $item['image_ids'] ) ? $item['image_ids'][0] : 0;
$title = ! empty( $item['title'] ) ? $item['title'] : '';
$image_url = ! empty( \aawp_build_product_image_url( $image_id ) ) ? \aawp_build_product_image_url( $image_id ) : AAWP_PLUGIN_URL . 'assets/img/placeholder-medium.jpg';
return sprintf( '<img src="%1$s" alt="%2$s" width="50" height="50" title="%3$s" />', $image_url, $title, $title );
}
/**
* Column Title.
*
* @param Object $item A listtable item.
*
* @since 3.20
*
* @return string
*/
public function column_title( $item ) {
$item = $this->products->get_product_by( 'id', $item->product_id );
if ( empty( $item ) ) {
return esc_html__( 'Product was removed from database.', 'aawp' );
}
if ( empty( $item['title'] ) ) {
return '-';
}
if ( strlen( esc_html( $item['title'] ) ) > 100 ) {
$truncate = '<div class="truncate">';
$truncate .= substr( esc_html( $item['title'] ), 0, 100 );
$truncate .= '<span class="ellipsis"> ...</span>';
$truncate .= '</div>';
$full = '<div class="full">' . esc_html( $item['title'] ) . '</div>';
return $truncate . $full;
}
return esc_html( $item['title'] );
}
/**
* Column link.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return int|string
*/
public function column_link( $item ) {
$product = $this->products->get_product_by( 'id', $item->product_id );
$url = ! empty( $product['url'] ) ? aawp_replace_url_tracking_id_placeholder( $product['url'], $item->tracking_id, false ) : '';
return esc_url( $url );
}
/**
* Column link Type.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return int|string
*/
public function column_link_type( $item ) {
return ! empty( $item->link_type ) ? esc_html( ucwords( str_replace( '_', ' ', $item->link_type ) ) ) : '';
}
/**
* Column Source Title.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_source( $item ) {
$source = $this->get_source( $item->source_type, absint( $item->source_id ) );
return sprintf( '<a href=%1s>%2s</a>', esc_url( $source['link'] ), esc_html( $source['title'] ) );
}
/**
* Column Referer URL.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_referer_url( $item ) {
return ! empty( $item->referer_url ) ? esc_url( $item->referer_url ) : '';
}
/**
* Column Tracking ID.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_tracking_id( $item ) {
return ! empty( $item->tracking_id ) ? $item->tracking_id : '-';
}
/**
* Column date.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_created_at( $item ) {
return esc_html( $item->created_at );
}
/**
* Column Browser
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_browser( $item ) {
return esc_html( $item->browser );
}
/**
* Column OS
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_os( $item ) {
return esc_html( $item->os );
}
/**
* Column Device
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_device( $item ) {
return esc_html( $item->device );
}
/**
* Column Country.
*
* @since 3.20
*
* @param \AAWP\ClickTracking\Click $item List table item.
*
* @return string
*/
public function column_country( $item ) {
return isset( $item->country ) ? esc_html( $item->country ) : '';
}
/**
* Returns the columns names for rendering.
*
* @since 3.20
*
* @return array
*/
public function get_columns() {
$columns = [
'id' => __( 'ID', 'aawp' ),
'asin' => __( 'ASIN', 'aawp' ),
'preview' => __( 'Preview', 'aawp' ),
'title' => __( 'Title', 'aawp' ),
'link_type' => __( 'Type', 'aawp' ),
'link' => __( 'Target', 'aawp' ),
'source' => __( 'Source', 'aawp' ),
'referer_url' => __( 'Referer URL', 'aawp' ),
'created_at' => __( 'Created At', 'aawp' ),
'browser' => __( 'Browser', 'aawp' ),
'os' => __( 'OS', 'aawp' ),
'device' => __( 'Device', 'aawp' ),
];
if ( ! empty( get_option( 'aawp_clicks_settings' )['country'] ) ) {
$columns['country'] = __( 'Country', 'aawp' );
}
return apply_filters( 'aawp_clicks_columns', $columns );
}
/**
* Get a list of sortable columns.
*
* @return array
*/
protected function get_sortable_columns() {
return [
'id' => [ 'id', 'asc' ],
'created_at' => [ 'created_at', 'asc' ],
'country' => [ 'country', 'asc' ],
'asin' => [ 'asin', true ],
'title' => [ 'title', true ],
];
}
/**
* Prepares the _column_headers property which is used by WP_Table_List at rendering.
* It merges the columns and the sortable columns.
*
* @since 3.20
*/
private function prepare_column_headers() {
$this->_column_headers = [
$this->get_columns(),
get_hidden_columns( $this->screen ),
$this->get_sortable_columns(),
];
}
/**
* Get hidden columns. These columns can be enabled by using Screen Options.
*
* @since 3.20
*/
protected function get_hidden_columns() {
return apply_filters( 'aawp_clicks_hidden_columns', [ 'browser', 'os', 'device' ] );
}
/**
* Filter the default list of hidden columns.
*
* @since 3.20s
*
* @param string[] $hidden Array of IDs of columns hidden by default.
* @param WP_Screen $screen WP_Screen object of the current screen.
*
* @return string[]
*/
public function default_hidden_columns( $hidden, $screen ) {
if ( $screen->id !== 'aawp_page_aawp-clicks' ) { //phpcs:ignore WordPress.PHP.YodaConditions.NotYoda
return $hidden;
}
return $this->get_hidden_columns( $this->screen );
}
/**
* Display date selector
*
* @since 3.20
*
* @return mixed HTML output
*/
public function date_select() {
?>
<div class="aawp-clicks-date-range-filter" style="display: none">
<label for="start-date" class="screen-reader-text"><?php esc_html_e( 'Start date', 'aawp' ); ?></label>
<?php if ( ! empty( $_REQUEST['startdate'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
<input class="filter-dates" placeholder="<?php esc_attr_e( 'Start date', 'aawp' ); ?>" name="startdate" id="start-date" type="date" value="<?php echo esc_attr( wp_unslash( $_REQUEST['startdate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>" />
<?php else : ?>
<input class="filter-dates" placeholder="<?php esc_attr_e( 'Start date', 'aawp' ); ?>" name="startdate" id="start-date" type="text" onfocus="(this.type='date')" />
<?php endif; ?>
<label for="end-date" class="screen-reader-text"><?php esc_html_e( 'End date', 'aawp' ); ?></label>
<?php if ( ! empty( $_REQUEST['enddate'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
<input class="filter-dates" placeholder="<?php esc_attr_e( 'End date', 'aawp' ); ?>" name="enddate" id="end-date" type="date" value="<?php echo esc_attr( wp_unslash( $_REQUEST['enddate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>" />
<?php else : ?>
<input class="filter-dates" placeholder="<?php esc_attr_e( 'End date', 'aawp' ); ?>" name="enddate" id="end-date" type="text" onfocus="(this.type='date')" />
<?php endif; ?>
</div>
<?php
}
/**
* Timespan select HTML.
*
* @since 3.20
*/
public function timespan_select_html() {
?>
<select id="aawp-clicks-graph-timespan" name="timespan" class="aawp-clicks-graph-select-timespan" title="<?php esc_attr_e( 'Select timespan', 'aawp' ); ?>" >
<?php
$this->timespan_options_html(
apply_filters(
'aawp_clicktracking_graph_timespan_options',
[
'today' => __( 'Today', 'aawp' ),
'yesterday' => __( 'Yesterday', 'aawp' ),
'last_7' => __( 'Last 7 days', 'aawp' ),
'last_30' => __( 'Last 30 days', 'aawp' ),
'this_month' => __( 'This month', 'aawp' ),
'last_month' => __( 'Last month', 'aawp' ),
'this_year' => __( 'This year', 'aawp' ),
'last_year' => __( 'Last year', 'aawp' ),
'all_time' => __( 'All time', 'aawp' ),
'custom' => __( 'Custom', 'aawp' ),
]
)
);
?>
</select>
<?php
}
/**
* Timespan select options HTML.
*
* @since 3.20
*
* @param array $options Timespan options (in days).
*/
protected function timespan_options_html( $options ) {
$timespan = ! empty( get_option( 'aawp_clicks_settings' )['timespan'] ) ? get_option( 'aawp_clicks_settings' )['timespan'] : 'last_30';
$timespan = ! empty( $_REQUEST['timespan'] ) && 'custom' === sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) ? 'custom' : $timespan; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
foreach ( $options as $value => $label ) :
?>
<option value="<?php echo sanitize_key( $value ); ?>" <?php selected( $timespan, sanitize_key( $value ) ); ?> >
<?php /* translators: %d - Number of days. */ ?>
<?php echo esc_html( $label ); ?>
</option>
<?php
endforeach;
}
/**
* Generate the table navigation above or below the table.
*
* @since 3.20
*
* @param string $which Which position.
*/
protected function display_tablenav( $which ) {
if ( ! $this->get_total() ) {
return;
}
?>
<div class="tablenav <?php echo esc_attr( $which ); ?>">
<?php
if ( 'top' === $which ) {
$this->extra_tablenav( $which );
}
$this->pagination( $which );
?>
<br class="clear" />
</div>
<?php
}
/**
* No items text.
*
* @since 3.20
*/
public function no_items() {
if ( ! $this->db->is_click_tracking_enabled() ) {
esc_html_e( 'Click Tracking is currently disabled.', 'aawp' );
} else {
esc_html_e( 'No clicks found.', 'aawp' );
}
}
/**
* Display list table page.
*
* @since 3.20
*/
public function display_page() {
$this->prepare_column_headers();
$this->prepare_items();
echo '<div class="aawp-list-table aawp-list-table--clicks">';
echo '<form action="admin.php?page=aawp-clicks" id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="post">';
parent::display();
echo wp_nonce_field( 'aawp_clicks_listtable_actions' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</form>';
echo '</div>';
}
/**
* Get prepared WHERE clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared WHERE clause for items query.
*/
public function get_items_query_where() {
global $wpdb;
$where_conditions = [];
$where_values = [];
if ( ! empty( $_REQUEST['timespan'] ) && 'custom' === sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) && ! empty( $_REQUEST['startdate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$where_conditions[] = 'DATE( created_at ) >= %s';
$where_values[] = sanitize_text_field( wp_unslash( $_REQUEST['startdate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
if ( ! empty( $_REQUEST['timespan'] ) && 'custom' === sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) && ! empty( $_REQUEST['enddate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$where_conditions[] = 'DATE( created_at ) <= %s';
$where_values[] = sanitize_text_field( wp_unslash( $_REQUEST['enddate'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
// Preset last X days dropdown.
if ( empty( $_REQUEST['startdate'] ) && empty( $_REQUEST['enddate'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$option = (array) get_option( 'aawp_clicks_settings' );
$timespan_db = ! empty( $option['timespan'] ) ? $option['timespan'] : '';
$timespan_rq = ! empty( $_REQUEST['timespan'] ) && 'custom' !== $_REQUEST['timespan'] ? sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) : $timespan_db; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
$option['timespan'] = $timespan_rq;
update_option( 'aawp_clicks_settings', $option );
switch ( $timespan_rq ) {
case 'today':
$condition = 'DATE( created_at ) = CURRENT_DATE()';
break;
case 'yesterday':
$condition = 'DATE( created_at ) = CURRENT_DATE() - 1';
break;
case 'last_7':
$condition = 'DATE( created_at ) >= DATE( NOW() - INTERVAL 7 DAY )';
break;
case 'last_30':
$condition = 'DATE( created_at ) >= DATE( NOW() - INTERVAL 30 DAY )';
break;
case 'this_month':
$condition = 'MONTH(created_at) = MONTH(CURRENT_DATE())
AND YEAR(created_at) = YEAR(CURRENT_DATE())';
break;
case 'last_month':
$condition = 'YEAR(created_at) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(created_at) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)';
break;
case 'this_year':
$condition = 'YEAR(created_at) = YEAR(CURRENT_DATE())';
break;
case 'last_year':
$condition = 'YEAR(created_at) = YEAR(CURRENT_DATE - INTERVAL 1 YEAR)';
break;
case 'all_time':
case 'custom':
$condition = '1 = 1';
break;
default:
$condition = '1 = 1';
}//end switch
return "WHERE 1 = 1 AND {$condition}";
}//end if
// When the timespan is not selected, handle where conditions this way because start date and end date both can be selected.
if ( ! empty( $where_conditions ) ) {
return $wpdb->prepare( 'WHERE 1 = 1 AND ' . implode( ' AND ', $where_conditions ), $where_values ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
} else {
return '';
}
}
/**
* Process admin actions buttons, such as filter dates.
*
* @since 3.20
*/
public function perform_actions() {
if ( empty( $_REQUEST['_wpnonce'] ) || empty( $_GET['page'] ) || 'aawp-clicks' !== $_GET['page'] ) {
return;
}
$verify = wp_verify_nonce( sanitize_key( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'aawp_clicks_listtable_actions' );
if ( ! empty( $_REQUEST['startdate'] ) && ! empty( $_REQUEST['enddate'] ) && $verify ) {
$start_date = sanitize_text_field( wp_unslash( $_REQUEST['startdate'] ) );
$end_date = sanitize_text_field( wp_unslash( $_REQUEST['enddate'] ) );
wp_safe_redirect( admin_url( 'admin.php?page=aawp-clicks&startdate=' . $start_date . '&enddate=' . $end_date ) );
exit();
} elseif ( ! empty( $_REQUEST['timespan'] ) && 'last_30' !== $_REQUEST['timespan'] ) {
wp_safe_redirect( admin_url( 'admin.php?page=aawp-clicks×pan=' . sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) ) );
exit();
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Get source Link & Title
*
* @param string $source_type The type of click source.
* @param int $source_id The ID of the source.
*
* @return array An array of Link & Title.
*/
public function get_source( $source_type, $source_id ) { //phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
switch ( $source_type ) {
case 'post':
$link = get_the_permalink( $source_id );
$title = get_the_title( $source_id );
break;
case 'term':
$link = get_term_link( absint( $source_id ) );
$title = get_term( $source_id )->name;
break;
case 'front_page':
$link = home_url();
$title = get_bloginfo( 'name', 'raw' );
break;
case 'posts_page':
$link = get_permalink( get_option( 'page_for_posts' ) );
$title = get_the_title( get_option( 'page_for_posts', true ) );
break;
default:
$link = '';
$title = '';
}//end switch
return [
'link' => $link,
'title' => ! empty( $title ) ? $title : '(no title)',
];
}
/**
* Check if the database table exist.
*
* @since 3.20
*
* @return bool
*/
public function table_exists() {
return $this->db->table_exists();
}
/**
* Get total clicks.
*
* @since 3.20
*
* @return int
*/
public function get_total() {
return $this->db->get_total();
}
}