array( 'key' => 'visitors', 'title' => __( 'Visitors', 'swiftwebdev-dashboard-analytics' ), 'tip' => __( 'Unique people who visited your site in this period, counted without cookies.', 'swiftwebdev-dashboard-analytics' ), 'tone' => '', ), 'page-views' => array( 'key' => 'views', 'title' => __( 'Page Views', 'swiftwebdev-dashboard-analytics' ), 'tip' => __( 'Total pages viewed, including repeat views of the same page.', 'swiftwebdev-dashboard-analytics' ), 'tone' => 'purple', ), 'sessions' => array( 'key' => 'sessions', 'title' => __( 'Sessions', 'swiftwebdev-dashboard-analytics' ), 'tip' => __( 'Separate visits to your site. One person can have several sessions.', 'swiftwebdev-dashboard-analytics' ), 'tone' => 'green', ), ); } /** * Stats for a period, fetched once per request. * * @param int $days Days in the period. * @return array See swiftdash_get_stats(). */ function swiftdash_analytics_stats( $days ) { static $cache = array(); if ( ! isset( $cache[ $days ] ) ) { $cache[ $days ] = swiftdash_get_stats( $days ); } return $cache[ $days ]; } /** * Periods the dashboard visitor widgets offer, in days => label. Daily totals are kept long * enough to compare the longest period with the one before it (see swiftdash_rollup()). * * @return string[] */ function swiftdash_analytics_ranges() { return array( 7 => __( 'Last 7 days', 'swiftwebdev-dashboard-analytics' ), 30 => __( 'Last 30 days', 'swiftwebdev-dashboard-analytics' ), 90 => __( 'Last 90 days', 'swiftwebdev-dashboard-analytics' ), ); } /** * Selected period in days. * * @return int */ function swiftdash_analytics_range() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only view filter. $range = isset( $_GET['range'] ) ? absint( $_GET['range'] ) : 30; return array_key_exists( $range, swiftdash_analytics_ranges() ) ? $range : 30; } /** * Period picker for the dashboard header. Changing it reloads the page (app.js). */ function swiftdash_render_range_picker() { $range = swiftdash_analytics_range(); ?>
$metric ) { swiftdash_register_widget( $id, array( 'title' => $metric['title'], 'description' => $metric['tip'], 'area' => 'kpis', 'order' => $order, 'span' => 4, 'render' => 'swiftdash_render_kpi_widget', 'metric' => $id, 'days' => $range, ) ); $order += 10; } $reports = array( 'visitor-trend' => array( __( 'Visitors over time', 'swiftwebdev-dashboard-analytics' ), __( 'Visitors and page views for each day of the selected period.', 'swiftwebdev-dashboard-analytics' ), 'overview', 10, 8, 'swiftdash_render_trend_widget' ), 'visitor-weekdays' => array( __( 'Busiest days', 'swiftwebdev-dashboard-analytics' ), __( 'Average visitors on each day of the week.', 'swiftwebdev-dashboard-analytics' ), 'overview', 20, 4, 'swiftdash_render_weekdays_widget' ), 'visitor-engagement' => array( __( 'Engagement', 'swiftwebdev-dashboard-analytics' ), __( 'How much people read and how often they come back.', 'swiftwebdev-dashboard-analytics' ), 'overview', 30, 12, 'swiftdash_render_engagement_widget' ), ); foreach ( $reports as $id => $r ) { swiftdash_register_widget( $id, array( 'title' => $r[0], 'description' => $r[1], 'area' => $r[2], 'order' => $r[3], 'span' => $r[4], 'render' => $r[5], 'days' => $range, ) ); } } add_action( 'swiftdash_register_widgets', 'swiftdash_analytics_register_widgets' ); /** * Register the visitor widget styles (add-ons can enqueue the handle on their own screens) * and load them on the dashboard. * * @param string $screen_key Current screen key. */ function swiftdash_analytics_assets( $screen_key ) { wp_register_style( 'swiftdash-analytics', SWIFTDASH_URL . 'assets/css/analytics.css', array( 'swiftdash-app' ), SWIFTDASH_VERSION ); if ( 'dashboard' === $screen_key ) { wp_enqueue_style( 'swiftdash-analytics' ); } } add_action( 'swiftdash_enqueue_assets', 'swiftdash_analytics_assets' ); /** * Change versus the previous period, for metrics where up is good. * * @param int $current Current total. * @param int $previous Previous total. * @return array tone (good, bad, flat), icon ('' for none), text, sr (screen-reader prefix). */ function swiftdash_kpi_change( $current, $previous ) { if ( $previous <= 0 ) { return array( 'tone' => 'flat', 'icon' => '', 'text' => $current > 0 ? __( 'New', 'swiftwebdev-dashboard-analytics' ) : __( 'No change', 'swiftwebdev-dashboard-analytics' ), 'sr' => '', ); } $pct = (int) round( ( $current - $previous ) / $previous * 100 ); if ( 0 === $pct ) { return array( 'tone' => 'flat', 'icon' => '', 'text' => __( 'No change', 'swiftwebdev-dashboard-analytics' ), 'sr' => '', ); } return array( 'tone' => $pct > 0 ? 'good' : 'bad', 'icon' => $pct > 0 ? 'arrow-up' : 'arrow-down', /* translators: %s: percentage number. */ 'text' => sprintf( __( '%s%%', 'swiftwebdev-dashboard-analytics' ), number_format_i18n( abs( $pct ) ) ), 'sr' => $pct > 0 ? __( 'Up', 'swiftwebdev-dashboard-analytics' ) : __( 'Down', 'swiftwebdev-dashboard-analytics' ), ); } /** * Inline SVG sparkline points for a series. * * @param int[] $values Values, oldest first. * @return string Points attribute for a 100x32 viewBox. */ function swiftdash_sparkline_points( $values ) { $max = max( 1, max( $values ) ); $step = 100 / max( 1, count( $values ) - 1 ); $points = array(); foreach ( array_values( $values ) as $i => $v ) { $points[] = round( $i * $step, 2 ) . ',' . round( 30 - ( $v / $max ) * 28, 2 ); } return implode( ' ', $points ); } /** * KPI card body. * * @param array $widget Widget args (metric, days). */ function swiftdash_render_kpi_widget( $widget ) { $metrics = swiftdash_kpi_metrics(); $metric = $metrics[ $widget['metric'] ]; $days = (int) $widget['days']; $stats = swiftdash_analytics_stats( $days ); $key = $metric['key']; ?>

1 ) : ?>
' . esc_html( $change['sr'] ) . ' '; } echo esc_html( $change['text'] ); ?>
= $since; } ) ); } /** * Empty state shared by the visitor report widgets. */ function swiftdash_render_report_empty() { echo '

' . esc_html__( 'No visits recorded yet', 'swiftwebdev-dashboard-analytics' ) . '

'; echo '

' . esc_html( swiftdash_analytics_empty_help() ) . '

'; } /** * Visitors over time: a bar per day with page views as a line. * * @param array $widget Widget args (days). */ function swiftdash_render_trend_widget( $widget ) { $stats = swiftdash_analytics_stats( (int) $widget['days'] ); if ( empty( $stats['has_data'] ) ) { swiftdash_render_report_empty(); return; } $buckets = $stats['daily']; $n = count( $buckets ); $max = max( 1, max( wp_list_pluck( $buckets, 'views' ) ), max( wp_list_pluck( $buckets, 'visitors' ) ) ); $height = 160; $points = array(); $format = get_option( 'date_format' ); $label = sprintf( /* translators: 1: total visitors, 2: total page views. */ __( 'Visitors over time. %1$s visitors and %2$s page views in this period.', 'swiftwebdev-dashboard-analytics' ), number_format_i18n( $stats['totals']['visitors'] ), number_format_i18n( $stats['totals']['views'] ) ); ?>
$b ) { $h = $b['visitors'] / $max * ( $height - 4 ); $when = date_i18n( $format, strtotime( $b['date'] ) ); $points[] = ( $i * 10 + 5 ) . ',' . round( $height - $b['views'] / $max * ( $height - 4 ), 2 ); printf( '%4$s', esc_attr( $i * 10 + 1.5 ), esc_attr( round( $height - max( $h, 0.5 ), 2 ) ), esc_attr( round( max( $h, 0.5 ), 2 ) ), esc_html( sprintf( /* translators: 1: date, 2: visitors, 3: page views. */ __( '%1$s: %2$s visitors, %3$s page views', 'swiftwebdev-dashboard-analytics' ), $when, number_format_i18n( $b['visitors'] ), number_format_i18n( $b['views'] ) ) ) ); } ?>
0 ) { /* translators: %s: weekday name. */ echo '

' . esc_html( sprintf( __( '%s is usually your busiest day.', 'swiftwebdev-dashboard-analytics' ), $wp_locale->get_weekday( $best ) ) ) . '

'; } echo ''; /* translators: %s: number of days. */ echo '

' . esc_html( sprintf( _n( 'Based on %s day of visits.', 'Based on %s days of visits.', count( $days ), 'swiftwebdev-dashboard-analytics' ), number_format_i18n( count( $days ) ) ) ) . '

'; } /** * Engagement: pages per visit, visits per visitor, average daily visitors and the best day. * * @param array $widget Widget args (days). */ function swiftdash_render_engagement_widget( $widget ) { $stats = swiftdash_analytics_stats( (int) $widget['days'] ); $days = swiftdash_analytics_active_days( $stats ); $t = $stats['totals']; if ( empty( $stats['has_data'] ) || ! $days || ! $t['views'] ) { swiftdash_render_report_empty(); return; } $best = array_reduce( $days, function ( $carry, $row ) { return null === $carry || $row['visitors'] > $carry['visitors'] ? $row : $carry; } ); $daily = $t['visitors'] / count( $days ); $items = array( array( 'file-text-fill', 'purple', __( 'Pages per visit', 'swiftwebdev-dashboard-analytics' ), number_format_i18n( $t['sessions'] ? $t['views'] / $t['sessions'] : 0, 1 ), __( 'Pages viewed in an average visit', 'swiftwebdev-dashboard-analytics' ) ), array( 'arrows-clockwise-fill', 'green', __( 'Visits per visitor', 'swiftwebdev-dashboard-analytics' ), number_format_i18n( $t['visitors'] ? $t['sessions'] / $t['visitors'] : 0, 1 ), __( 'Visits by the same person in a day', 'swiftwebdev-dashboard-analytics' ) ), array( 'users-fill', 'blue', __( 'Visitors per day', 'swiftwebdev-dashboard-analytics' ), number_format_i18n( $daily, $daily < 10 ? 1 : 0 ), __( 'Average over the days with data', 'swiftwebdev-dashboard-analytics' ) ), array( 'trend-up', 'orange', __( 'Best day', 'swiftwebdev-dashboard-analytics' ), date_i18n( _x( 'M j', 'best day date format', 'swiftwebdev-dashboard-analytics' ), strtotime( $best['date'] ) ), /* translators: %s: number of visitors. */ sprintf( _n( '%s visitor', '%s visitors', $best['visitors'], 'swiftwebdev-dashboard-analytics' ), number_format_i18n( $best['visitors'] ) ), ), ); echo ''; }