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; } }