get( 'analytics_replace_dashboard', false ) ) ) {
remove_action( 'welcome_panel', 'wp_welcome_panel' );
global $wp_meta_boxes;
if ( isset( $wp_meta_boxes['dashboard'] ) ) {
unset( $wp_meta_boxes['dashboard'] );
}
}
wp_add_dashboard_widget( 'accessiq_quick_stats', __( '📊 AccessIQ — Quick Stats', 'accessiq' ), array( $this, 'render_quick_stats_widget' ) );
wp_add_dashboard_widget( 'accessiq_top_pages', __( '🔥 Top Pages (Last 7 Days)', 'accessiq' ), array( $this, 'render_top_pages_widget' ) );
wp_add_dashboard_widget( 'accessiq_device_chart', __( '📱 Device Breakdown', 'accessiq' ), array( $this, 'render_device_chart_widget' ) );
wp_add_dashboard_widget( 'accessiq_recent_activity', __( 'âš¡ Recent Clicks', 'accessiq' ), array( $this, 'render_recent_activity_widget' ) );
}
public function render_quick_stats_widget() {
echo '
';
}
public function render_top_pages_widget() {
echo '';
}
public function render_device_chart_widget() {
echo '
';
}
public function render_recent_activity_widget() {
echo '';
}
/**
* Enqueue WP dashboard assets.
*/
public function enqueue_dashboard_assets( $hook ) {
if ( $hook !== 'index.php' && strpos( $hook, 'accessiq' ) === false ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_enqueue_style(
'accessiq-dashboard-widgets',
ACCESSIQ_URL . 'admin/css/dashboard-widgets.css',
array(),
ACCESSIQ_VERSION
);
wp_enqueue_script(
'accessiq-chartjs',
ACCESSIQ_URL . 'admin/js/chart.min.js',
array(),
'4.4.8',
true
);
wp_enqueue_script(
'accessiq-analytics-dashboard',
ACCESSIQ_URL . 'admin/js/analytics-dashboard.js',
array( 'jquery', 'accessiq-chartjs' ),
ACCESSIQ_VERSION,
true
);
wp_localize_script( 'accessiq-analytics-dashboard', 'accessiqDashboardData', array(
'nonce' => wp_create_nonce( 'accessiq_admin_nonce' ),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'quick_stats' => $this->get_quick_stats_data(),
'top_pages' => $this->get_top_pages_data(),
'device_data' => $this->get_device_data(),
'recent_clicks' => $this->get_recent_clicks_data(),
) );
}
/**
* Quick Stats (today sessions, total clicks, avg time, bounce rate).
*/
private function get_quick_stats_data() {
global $wpdb;
$sessions_table = $wpdb->prefix . 'accessiq_sessions';
$clicks_table = $wpdb->prefix . 'accessiq_clicks';
$today_start = current_time( 'Y-m-d' ) . ' 00:00:00';
$cutoff_7d = gmdate( 'Y-m-d H:i:s', strtotime( '-7 days', current_time( 'timestamp' ) ) );
$cache_key = 'accessiq_dash_today_' . current_time( 'Y-m-d' );
$today_sessions = wp_cache_get( $cache_key, 'accessiq' );
if ( false === $today_sessions ) {
$today_sessions = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT COUNT(*) FROM `' . esc_sql( $sessions_table ) . '` WHERE created_at >= %s',
$today_start
)
);
wp_cache_set( $cache_key, $today_sessions, 'accessiq', 300 );
}
$cache_key2 = 'accessiq_dash_total_clicks';
$total_clicks = wp_cache_get( $cache_key2, 'accessiq' );
if ( false === $total_clicks ) {
$total_clicks = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
'SELECT COUNT(*) FROM `' . esc_sql( $clicks_table ) . '`' // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
wp_cache_set( $cache_key2, $total_clicks, 'accessiq', 300 );
}
$cache_key3 = 'accessiq_dash_avg_time_7d';
$avg_sec = wp_cache_get( $cache_key3, 'accessiq' );
if ( false === $avg_sec ) {
$avg_sec = (float) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT AVG(time_spent) FROM `' . esc_sql( $sessions_table ) . '` WHERE created_at >= %s AND time_spent < 3600',
$cutoff_7d
)
);
wp_cache_set( $cache_key3, $avg_sec, 'accessiq', 300 );
}
$avg_sec = (int) round( (float) $avg_sec ); // whole seconds: % on a float is deprecated in PHP 8.1+
$minutes = intdiv( $avg_sec, 60 );
$seconds = $avg_sec % 60;
$avg_time = "{$minutes}m {$seconds}s";
$cache_key4 = 'accessiq_dash_total_7d';
$total_7days = wp_cache_get( $cache_key4, 'accessiq' );
if ( false === $total_7days ) {
$total_7days = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT COUNT(*) FROM `' . esc_sql( $sessions_table ) . '` WHERE created_at >= %s',
$cutoff_7d
)
);
wp_cache_set( $cache_key4, $total_7days, 'accessiq', 300 );
}
$cache_key5 = 'accessiq_dash_bounce_7d';
$bounced_7days = wp_cache_get( $cache_key5, 'accessiq' );
if ( false === $bounced_7days ) {
$bounced_7days = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT COUNT(*) FROM `' . esc_sql( $sessions_table ) . '` WHERE created_at >= %s AND is_bounce = 1',
$cutoff_7d
)
);
wp_cache_set( $cache_key5, $bounced_7days, 'accessiq', 300 );
}
$bounce_rate = $total_7days > 0 ? round( ( $bounced_7days / $total_7days ) * 100, 2 ) : 0;
$pv_table = $wpdb->prefix . 'accessiq_page_views';
$cache_key6 = 'accessiq_dash_avg_pages';
$avg_pages = wp_cache_get( $cache_key6, 'accessiq' );
if ( false === $avg_pages ) {
$pv_total = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
'SELECT COUNT(*) FROM `' . esc_sql( $pv_table ) . '`' // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
$pv_sessions = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
'SELECT COUNT(DISTINCT session_id) FROM `' . esc_sql( $pv_table ) . '`' // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
$avg_pages = $pv_sessions > 0 ? round( $pv_total / $pv_sessions, 1 ) : 0;
wp_cache_set( $cache_key6, $avg_pages, 'accessiq', 300 );
}
return array(
'today_sessions' => $today_sessions,
'total_clicks' => $total_clicks,
'avg_time' => $avg_time,
'bounce_rate' => $bounce_rate,
'avg_pages' => $avg_pages,
);
}
/**
* Top Pages (last 7 days).
*/
private function get_top_pages_data() {
global $wpdb;
$table = $wpdb->prefix . 'accessiq_sessions';
$cache_key = 'accessiq_dash_top_pages_7d';
$pages = wp_cache_get( $cache_key, 'accessiq' );
if ( false === $pages ) {
$cutoff_7d_pages = gmdate( 'Y-m-d H:i:s', strtotime( '-7 days', current_time( 'timestamp' ) ) );
$pages = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT page_url, COUNT(*) as sessions, SUM(CASE WHEN is_bounce = 1 THEN 1 ELSE 0 END) as bounce_count
FROM `' . esc_sql( $table ) . '`
WHERE created_at >= %s
AND page_url NOT LIKE \'%/wp-content/%\'
AND page_url NOT LIKE \'%/wp-admin/%\'
AND page_url NOT LIKE \'%.php%\'
AND page_url NOT LIKE \'%/wp-json/%\'
GROUP BY page_url ORDER BY sessions DESC LIMIT 5',
$cutoff_7d_pages
)
);
wp_cache_set( $cache_key, $pages, 'accessiq', 300 );
}
$result = array();
$max_sessions = 0;
foreach ( (array) $pages as $p ) {
if ( $p->sessions > $max_sessions ) {
$max_sessions = (int) $p->sessions;
}
$path = wp_parse_url( $p->page_url, PHP_URL_PATH );
$result[] = array(
'page_url' => $path ? $path : $p->page_url,
'sessions' => (int) $p->sessions,
'bounce_rate' => $p->sessions > 0 ? round( ( $p->bounce_count / $p->sessions ) * 100, 2 ) : 0,
);
}
return array(
'max_sessions' => $max_sessions > 0 ? $max_sessions : 1,
'pages' => $result,
);
}
/**
* Device breakdown (last 30 days).
*/
private function get_device_data() {
global $wpdb;
$table = $wpdb->prefix . 'accessiq_sessions';
$cache_key = 'accessiq_dash_devices_30d';
$devices = wp_cache_get( $cache_key, 'accessiq' );
if ( false === $devices ) {
$cutoff_30d = gmdate( 'Y-m-d H:i:s', strtotime( '-30 days', current_time( 'timestamp' ) ) );
$devices = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'SELECT device_type, COUNT(*) as count FROM `' . esc_sql( $table ) . '`
WHERE created_at >= %s GROUP BY device_type',
$cutoff_30d
)
);
wp_cache_set( $cache_key, $devices, 'accessiq', 600 );
}
$data = array( 'desktop' => 0, 'tablet' => 0, 'mobile' => 0 );
foreach ( (array) $devices as $d ) {
$type = strtolower( $d->device_type );
if ( isset( $data[ $type ] ) ) {
$data[ $type ] = (int) $d->count;
}
}
return $data;
}
/**
* Recent clicks (last 8).
*/
private function get_recent_clicks_data() {
global $wpdb;
$table = $wpdb->prefix . 'accessiq_clicks';
$cache_key = 'accessiq_dash_recent_clicks';
$clicks = wp_cache_get( $cache_key, 'accessiq' );
if ( false === $clicks ) {
$clicks = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
'SELECT element, page_url, created_at FROM `' . esc_sql( $table ) . '` ORDER BY created_at DESC LIMIT 8' // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
wp_cache_set( $cache_key, $clicks, 'accessiq', 60 );
}
$result = array();
foreach ( (array) $clicks as $c ) {
$path = wp_parse_url( $c->page_url, PHP_URL_PATH );
$result[] = array(
'element' => wp_trim_words( $c->element, 5, '...' ),
'page_url' => $path ? $path : $c->page_url,
'created_at' => $c->created_at,
);
}
return $result;
}
}