Plugin Directory

Changeset 3378632


Ignore:
Timestamp:
10/15/2025 06:39:31 AM (2 months ago)
Author:
DvanKooten
Message:

v2.0.19

Location:
koko-analytics
Files:
14 added
2 deleted
44 edited
1 copied

Legend:

Unmodified
Added
Removed
  • koko-analytics/tags/2.0.19/CHANGELOG.md

    r3366958 r3378632  
    11# Changelog
     2
     3### 2.0.19 - Oct 15, 2025
     4
     5- Print (< 500 bytes) tracking script inline in page HTML to save on an additional HTTP request and resolve overly aggressive cache issues.
     6- Add importer for Plausible.
     7- Change public dashboard URL to `/koko-analytics-dashboard/` if pretty permalinks are enabled.
     8- Exclude visits to post previews.
     9
    210
    311### 2.0.18 - Sep 24, 2025
  • koko-analytics/tags/2.0.19/assets/dist/js/script.js

    r3362953 r3378632  
    1 /*! For license information please see script.js.LICENSE.txt */
    2 !function(){var e=window,t="koko_analytics";e[t].trackPageview=function(){if("prerender"!=document.visibilityState&&!/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview/i.test(navigator.userAgent)){var a=e[t].path;a||(a=window.location.pathname+window.location.search);var o,r=0==document.referrer.indexOf(e[t].site_url)?"":document.referrer;o={pa:a,po:e[t].post_id,r:r},e[t].use_cookie?o.m="c":e[t].method&&(o.m=e[t].method[0]),navigator.sendBeacon(e[t].url,new URLSearchParams(o))}},e.addEventListener("load",function(){e[t].trackPageview()})}();
     1!function(){var e=window,r="koko_analytics";function t(t){t.m=e[r].use_cookie?"c":e[r].method[0],navigator.sendBeacon(e[r].url,new URLSearchParams(t))}e[r].request=t,e[r].trackPageview=function(){if("prerender"!=document.visibilityState&&!/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview/i.test(navigator.userAgent)){var i=0==document.referrer.indexOf(e[r].site_url)?"":document.referrer;t({pa:e[r].path,po:e[r].post_id,r:i})}},e.addEventListener("load",function(){e[r].trackPageview()})}();
  • koko-analytics/tags/2.0.19/data/referrer-blocklist

    r3359615 r3378632  
    841841getrichquick.ml
    842842getrichquickly.info
     843gevciamst.online
    843844gezlev.com.ua
    844845ghazel.ru
     
    15071508pfrf-kabinet.ru
    15081509pharm--shop.ru
     1510phimarshcer.online
    15091511phimmakinhdi.com
    15101512photo-clip.ru
  • koko-analytics/tags/2.0.19/koko-analytics.php

    r3366958 r3378632  
    44Plugin Name: Koko Analytics
    55Plugin URI: https://www.kokoanalytics.com/#utm_source=wp-plugin&utm_medium=koko-analytics&utm_campaign=plugins-page
    6 Version: 2.0.18
     6Version: 2.0.19
    77Description: Privacy-friendly and efficient statistics for your WordPress site.
    88Author: ibericode
     
    3939use KokoAnalytics\Widgets\Most_Viewed_Posts_Widget;
    4040
    41 \define('KOKO_ANALYTICS_VERSION', '2.0.18');
     41\define('KOKO_ANALYTICS_VERSION', '2.0.19');
    4242\define('KOKO_ANALYTICS_PLUGIN_FILE', __FILE__);
    4343\define('KOKO_ANALYTICS_PLUGIN_DIR', __DIR__);
     
    7272
    7373// script loader
    74 add_action('wp_enqueue_scripts', [Script_Loader::class, 'maybe_enqueue_script'], 10, 0);
     74add_action('wp_head', [ Script_Loader::class , 'print_js_object' ], 1, 0);
     75add_action('wp_footer', [Script_Loader::class, 'maybe_print_script'], 10, 0);
    7576add_action('amp_print_analytics', [Script_Loader::class, 'print_amp_analytics_tag'], 10, 0);
    7677add_action('admin_bar_menu', [Admin\Bar::class, 'register'], 40, 1);
     
    108109// maybe show standalone dashboard
    109110add_action('wp', function () {
    110     if (!isset($_GET['koko-analytics-dashboard'])) {
     111    if (!Router::is('dashboard-standalone')) {
    111112        return;
    112113    }
     
    122123    }
    123124
    124     (new Dashboard())->show_standalone_dashboard_page();
     125    (new Dashboard_Standalone())->show();
     126    exit;
    125127}, 10, 0);
    126128
     
    134136}
    135137
    136 // on plugin update (but using old code)
    137 // this breaks in 2.x because of the new file structure
    138 // TODO: Reactivate once 2.x stabilises
    139 // add_filter('upgrader_process_complete', function () {
    140 //     do_action('koko_analytics_aggregate_stats');
    141 // });
     138// on plugin update (but using old code that's already in memory)
     139add_filter('upgrader_process_complete', function () {
     140    do_action('koko_analytics_aggregate_stats');
     141});
    142142
    143143// on plugin activation
  • koko-analytics/tags/2.0.19/readme.txt

    r3373525 r3378632  
    55Requires at least: 6.0
    66Tested up to: 6.8
    7 Stable tag: 2.0.18
     7Stable tag: 2.0.19
    88License: GPL-3.0-or-later
    99License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    132132== Changelog ==
    133133
     134### 2.0.19 - Oct 15, 2025
     135
     136- Print (< 500 bytes) tracking script inline in page HTML to save on an additional HTTP request and resolve overly aggressive cache issues.
     137- Add importer for Plausible.
     138- Change public dashboard URL to `/koko-analytics-dashboard/` if pretty permalinks are enabled.
     139- Exclude visits to post previews.
     140
     141
    134142### 2.0.18 - Sep 24, 2025
    135143
     
    444452#### 1.4.4 - Nov 4, 2024
    445453
    446 - Add Jetpack Stats importer to import your historical analytics data into Koko Analytics. Go to the settings page (with Jetpack still enabled) to access it.
    447 - Fix settings page showing proxy IP instead of client IP if using reverse proxy.
    448 - Fix use of PHP 7.4 only feature in thousands separator in source ...
    449 
     454- Add Jetpack Stats ...
     455
  • koko-analytics/tags/2.0.19/src/Admin/Actions.php

    r3366958 r3378632  
    193193
    194194        do {
    195             $results = $wpdb->get_results($wpdb->prepare("SELECT post_id, path_id, p.path FROM {$wpdb->prefix}koko_analytics_post_stats s LEFT JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id WHERE post_id IS NOT NULL AND post_id != 0 AND date <= '2025-08-29' GROUP BY post_id LIMIT %d OFFSET %d", [$limit, $offset]));
     195            $results = $wpdb->get_results($wpdb->prepare("SELECT post_id, path_id, p.path FROM {$wpdb->prefix}koko_analytics_post_stats s LEFT JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id WHERE post_id IS NOT NULL AND post_id != 0 GROUP BY post_id LIMIT %d OFFSET %d", [$limit, $offset]));
    196196            $offset += $limit;
    197197            if (!$results) {
  • koko-analytics/tags/2.0.19/src/Admin/Admin.php

    r3366958 r3378632  
    99namespace KokoAnalytics\Admin;
    1010
    11 use KokoAnalytics\Jetpack_Importer;
     11use KokoAnalytics\Import\Jetpack_Importer;
     12use KokoAnalytics\Import\Plausible_Importer;
     13use KokoAnalytics\Router;
    1214
    1315class Admin
     
    4143        add_action('koko_analytics_start_jetpack_import', [Jetpack_Importer::class, 'start_import'], 10, 0);
    4244        add_action('koko_analytics_jetpack_import_chunk', [Jetpack_Importer::class, 'import_chunk'], 10, 0);
     45
     46        // actions for plausible importer
     47        add_action('koko_analytics_show_plausible_importer_page', [Plausible_Importer::class, 'show_page'], 10, 0);
     48        add_action('koko_analytics_start_plausible_import', [Plausible_Importer::class, 'start_import'], 10, 0);
    4349    }
    4450
     
    5864    public function add_plugin_settings_link($links): array
    5965    {
    60         $href = admin_url('index.php?page=koko-analytics&tab=settings');
     66        $href = Router::url('settings-page');
    6167        $label = esc_html__('Settings', 'koko-analytics');
    6268        $settings_link = "<a href=\"{$href}\">{$label}</a>";
  • koko-analytics/tags/2.0.19/src/Admin/Data_Export.php

    r3366958 r3378632  
    99namespace KokoAnalytics\Admin;
    1010
    11 use function KokoAnalytics\create_local_datetime;
     11use DateTimeImmutable;
    1212
    1313class Data_Export
     
    3636    {
    3737        // write to HTTP stream
    38         $date = create_local_datetime('now')->format("Y-m-d");
     38        $date = (new DateTimeImmutable('now', wp_timezone()))->format("Y-m-d");
    3939        $site_url = parse_url(get_site_url(), PHP_URL_HOST);
    4040
  • koko-analytics/tags/2.0.19/src/Dashboard.php

    r3362953 r3378632  
    99namespace KokoAnalytics;
    1010
     11use DateTimeImmutable;
     12
    1113class Dashboard
    1214{
    13     public function show_standalone_dashboard_page(): void
    14     {
    15         require KOKO_ANALYTICS_PLUGIN_DIR . '/src/Resources/views/standalone.php';
    16         exit;
    17     }
    18 
    19     public function show(): void
     15    protected function get_base_url()
     16    {
     17        return Router::url('dashboard-embedded');
     18    }
     19
     20    public function show()
    2021    {
    2122        $settings   = get_settings();
    2223        $stats = new Stats();
    2324        $items_per_page = (int) apply_filters('koko_analytics_items_per_page', 20);
    24         $dateFormat = get_option('date_format', 'Y-m-d');
    25         $dashboard_url = remove_query_arg(['start_date', 'end_date', 'view', 'posts', 'referrers']);
     25        $date_format = get_option('date_format', 'Y-m-d');
     26        $dashboard_url = $this->get_base_url();
    2627
    2728        // parse query params
     
    3334            $range = $settings['default_view'];
    3435        }
    35         $now = create_local_datetime('now');
     36        $timezone = wp_timezone();
     37        $now = new DateTimeImmutable('now', $timezone);
    3638        $week_starts_on = (int) get_option('start_of_week', 0);
    37         $dateRange = $this->get_dates_for_range($now, $range, $week_starts_on);
     39        $date_range = $this->get_dates_for_range($now, $range, $week_starts_on);
    3840        $page = isset($_GET['p']) ? trim($_GET['p']) : 0;
    3941
    4042        try {
    41             $dateStart  = isset($_GET['start_date']) ? create_local_datetime($_GET['start_date']) : $dateRange[0];
     43            $date_start  = isset($_GET['start_date']) ? new DateTimeImmutable($_GET['start_date'], $timezone) : $date_range[0];
    4244        } catch (\Exception $e) {
    43             $dateStart = $dateRange[0];
     45            $date_start = $date_range[0];
    4446        }
    4547        try {
    46             $dateEnd    = isset($_GET['end_date']) ? create_local_datetime($_GET['end_date']) : $dateRange[1];
     48            $date_end    = isset($_GET['end_date']) ? new DateTimeImmutable($_GET['end_date'], $timezone) : $date_range[1];
    4749        } catch (\Exception $e) {
    48             $dateEnd = $dateRange[1];
     50            $date_end = $date_range[1];
    4951        }
    5052
     
    5759
    5860        // calculate next and previous dates for datepicker component and comparison
    59         $nextDates = $this->get_next_period($dateStart, $dateEnd, 1);
    60         $prevDates = $this->get_next_period($dateStart, $dateEnd, -1);
    61 
    62         $dateStartStr = $dateStart->format('Y-m-d');
    63         $dateEndStr = $dateEnd->format('Y-m-d');
    64 
    65         $totals = $stats->get_totals($dateStartStr, $dateEndStr, $page);
    66         $totals_previous = $stats->get_totals($prevDates[0]->format('Y-m-d'), $prevDates[2]->format('Y-m-d'), $page);
    67 
    68         $posts = $stats->get_posts($dateStartStr, $dateEndStr, $posts_offset, $posts_limit);
    69         $posts_count = $stats->count_posts($dateStartStr, $dateEndStr);
    70         $referrers = $stats->get_referrers($dateStartStr, $dateEndStr, $referrers_offset, $referrers_limit);
    71         $referrers_count = $stats->count_referrers($dateStartStr, $dateEndStr);
     61        $next_dates = $this->get_next_period($date_start, $date_end, 1);
     62        $prev_dates = $this->get_next_period($date_start, $date_end, -1);
     63
     64        $date_start_str = $date_start->format('Y-m-d');
     65        $date_end_str = $date_end->format('Y-m-d');
     66
     67        $totals = $stats->get_totals($date_start_str, $date_end_str, $page);
     68        $totals_previous = $stats->get_totals($prev_dates[0]->format('Y-m-d'), $prev_dates[2]->format('Y-m-d'), $page);
     69
     70        $posts = $stats->get_posts($date_start_str, $date_end_str, $posts_offset, $posts_limit);
     71        $posts_count = $stats->count_posts($date_start_str, $date_end_str);
     72        $referrers = $stats->get_referrers($date_start_str, $date_end_str, $referrers_offset, $referrers_limit);
     73        $referrers_count = $stats->count_referrers($date_start_str, $date_end_str);
    7274        $realtime = get_realtime_pageview_count('-1 hour');
    7375
    7476        if (isset($_GET['group']) && in_array($_GET['group'], ['day', 'week', 'month'])) {
    75             $groupChartBy = $_GET['group'];
    76         } else {
    77             $groupChartBy = $dateEnd->getTimestamp() - $dateStart->getTimestamp() >= 86400 * 90 ? 'month' : 'day';
    78         }
    79         $chart_data =  $stats->get_stats($dateStartStr, $dateEndStr, $groupChartBy, $page);
     77            $group_chart_by = $_GET['group'];
     78        } else {
     79            $group_chart_by = $date_end->getTimestamp() - $date_start->getTimestamp() >= 86400 * 90 ? 'month' : 'day';
     80        }
     81        $chart_data =  $stats->get_stats($date_start_str, $date_end_str, $group_chart_by, $page);
    8082
    8183        require KOKO_ANALYTICS_PLUGIN_DIR . '/src/Resources/views/dashboard-page.php';
    8284    }
    8385
    84     public function get_next_period(\DateTimeImmutable $dateStart, \DateTimeImmutable $dateEnd, int $dir = 1): array
     86    public function get_next_period(\DateTimeImmutable $date_start, \DateTimeImmutable $date_end, int $dir = 1): array
    8587    {
    8688        $now = new \DateTimeImmutable('now', wp_timezone());
    8789        $modifier = $dir > 0 ? "+" : "-";
    8890
    89         if ($dateStart->format('d') === "01" && $dateEnd->format('d') === $dateEnd->format('t')) {
     91        if ($date_start->format('d') === "01" && $date_end->format('d') === $date_end->format('t')) {
    9092            // cycling full months
    91             $diffInMonths = 1 + ((int) $dateEnd->format('Y') - (int) $dateStart->format('Y')) * 12 + (int) $dateEnd->format('m') - (int) $dateStart->format('m');
    92             $periodStart = $dateStart->setDate((int) $dateStart->format('Y'), (int) $dateStart->format('m') + ($dir * $diffInMonths), 1);
    93             $periodEnd = $dateEnd->setDate((int) $dateStart->format('Y'), (int) $dateEnd->format('m') + ($dir * $diffInMonths), 5);
     93            $diffInMonths = 1 + ((int) $date_end->format('Y') - (int) $date_start->format('Y')) * 12 + (int) $date_end->format('m') - (int) $date_start->format('m');
     94            $periodStart = $date_start->setDate((int) $date_start->format('Y'), (int) $date_start->format('m') + ($dir * $diffInMonths), 1);
     95            $periodEnd = $date_end->setDate((int) $date_start->format('Y'), (int) $date_end->format('m') + ($dir * $diffInMonths), 5);
    9496            $periodEnd = $periodEnd->setDate((int) $periodEnd->format('Y'), (int) $periodEnd->format('m'), (int) $periodEnd->format('t'));
    9597        } else {
    96             $diffInDays = $dateEnd->diff($dateStart)->days + 1;
    97             $periodStart = $dateStart->modify("{$modifier}{$diffInDays} days");
    98             $periodEnd = $dateEnd->modify("{$modifier}{$diffInDays} days");
    99         }
    100 
    101         if ($dateEnd > $now) {
     98            $diffInDays = $date_end->diff($date_start)->days + 1;
     99            $periodStart = $date_start->modify("{$modifier}{$diffInDays} days");
     100            $periodEnd = $date_end->modify("{$modifier}{$diffInDays} days");
     101        }
     102
     103        if ($date_end > $now) {
    102104            // limit end date to difference between now and start date, counting from start date
    103             $days_diff = $now->diff($dateStart)->days;
     105            $days_diff = $now->diff($date_start)->days;
    104106            $compareEnd = $periodStart->modify("+{$days_diff} days");
    105107        } else {
    106108            $compareEnd = $periodEnd;
    107109        }
    108 
    109110
    110111        return [ $periodStart, $periodEnd, $compareEnd ];
  • koko-analytics/tags/2.0.19/src/Dashboard_Widget.php

    r3350963 r3378632  
    88
    99namespace KokoAnalytics;
     10
     11use DateTimeImmutable;
    1012
    1113class Dashboard_Widget
     
    2729
    2830        $number_of_top_items = (int) apply_filters('koko_analytics_dashboard_widget_number_of_top_items', 5);
     31        $timezone = wp_timezone();
    2932        $stats = new Stats();
    30         $dateToday = create_local_datetime('today, midnight')->format('Y-m-d');
    31         $totals = $stats->get_totals($dateToday, $dateToday);
     33        $today = (new DateTimeImmutable('today, midnight', $timezone))->format('Y-m-d');
     34        $totals = $stats->get_totals($today, $today);
    3235
    3336        // get realtime pageviews, but limit it to number of total pageviews today in case viewing shortly after midnight
    3437        $realtime = min($totals->pageviews, get_realtime_pageview_count('-1 hour'));
    3538
    36         $dateStart = create_local_datetime('-14 days');
    37         $dateEnd = create_local_datetime('now');
    38         $chart_data = $stats->get_stats($dateStart->format('Y-m-d'), $dateEnd->format('Y-m-d'), 'day');
     39        // get chart data
     40        $date_start = new DateTimeImmutable('-14 days', $timezone);
     41        $date_end = new DateTimeImmutable('now', $timezone);
     42        $chart_data = $stats->get_stats($date_start->format('Y-m-d'), $date_end->format('Y-m-d'), 'day');
    3943
    4044        if ($number_of_top_items > 0) {
    41             $posts = $stats->get_posts($dateToday, $dateToday, 0, $number_of_top_items);
    42             $referrers = $stats->get_referrers($dateToday, $dateToday, 0, $number_of_top_items);
     45            $posts = $stats->get_posts($today, $today, 0, $number_of_top_items);
     46            $referrers = $stats->get_referrers($today, $today, 0, $number_of_top_items);
    4347        }
    4448
  • koko-analytics/tags/2.0.19/src/Pageview_Aggregator.php

    r3352477 r3378632  
    124124        global $wpdb;
    125125
    126         // insert referrer stats
     126        // insert page-specific stats
    127127        foreach ($this->post_stats as $date => $stats) {
    128             $paths = array_keys($stats);
    129             $path_map = Path_Repository::upsert($paths);
    130 
    131             // insert referrer stats
     128            $path_ids = Path_Repository::upsert(array_keys($stats));
    132129            $values = [];
    133130            foreach ($stats as $path => $r) {
    134                 array_push($values, $date, $path_map[$path], $r['post_id'], $r['visitors'], $r['pageviews']);
     131                array_push($values, $date, $path_ids[$path], $r['post_id'], $r['visitors'], $r['pageviews']);
    135132            }
    136133            $placeholders = rtrim(str_repeat('(%s,%d,%d,%d,%d),', count($stats)), ',');
     
    147144        // insert referrer stats
    148145        foreach ($this->referrer_stats as $date => $stats) {
    149             // retrieve ID's for known referrer urls
    150             $referrer_urls = array_keys($stats);
    151             $placeholders  = rtrim(str_repeat('%s,', count($referrer_urls)), ',');
    152             $results       = $wpdb->get_results($wpdb->prepare("SELECT id, url FROM {$wpdb->prefix}koko_analytics_referrer_urls r WHERE r.url IN({$placeholders})", $referrer_urls));
    153             foreach ($results as $r) {
    154                 $stats[ $r->url ]['id'] = $r->id;
    155             }
    156 
    157             // build query for new referrer urls
    158             $new_referrer_urls = [];
     146            $referrer_ids = Referrer_Repository::upsert(array_keys($stats));
     147            $values = [];
    159148            foreach ($stats as $url => $r) {
    160                 if (! isset($r['id'])) {
    161                     $new_referrer_urls[] = $url;
    162                 }
    163             }
    164 
    165             // insert new referrer urls and set ID in map
    166             if (count($new_referrer_urls) > 0) {
    167                 $values       = $new_referrer_urls;
    168                 $placeholders = rtrim(str_repeat('(%s),', count($values)), ',');
    169                 $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->prefix}koko_analytics_referrer_urls(url) VALUES {$placeholders}", $values));
    170                 $last_insert_id = $wpdb->insert_id;
    171                 foreach ($values as $url) {
    172                     $stats[ $url ]['id'] = $last_insert_id++;
    173                 }
    174             }
    175 
    176             // insert referrer stats
    177             $values = [];
    178             foreach ($stats as $r) {
    179                 array_push($values, $date, $r['id'], $r['visitors'], $r['pageviews']);
     149                array_push($values, $date, $referrer_ids[$url], $r['visitors'], $r['pageviews']);
    180150            }
    181151            $placeholders = rtrim(str_repeat('(%s,%d,%d,%d),', count($stats)), ',');
     
    192162        // remove all data older than 60 minutes
    193163        $one_hour_ago = \time() - 60 * 60;
    194         foreach ($counts as $timestamp => $v) {
     164        foreach ($counts as $timestamp => $unused) {
    195165            // delete all data older than one hour
    196166            if ((int) $timestamp < $one_hour_ago) {
  • koko-analytics/tags/2.0.19/src/Resources/functions/functions.php

    r3352698 r3378632  
    121121function get_realtime_pageview_count($since = null): int
    122122{
    123     if (is_numeric($since) || is_int($since)) {
     123    if (is_numeric($since)) {
    124124        $since = (int) $since;
    125     } elseif (is_string($since)) {
    126         // $since is relative time string
    127         $since = strtotime($since);
    128125    } else {
    129         $since = strtotime('-5 minutes');
     126        $since = strtotime($since ?? '-5 minutes');
    130127    }
    131128
  • koko-analytics/tags/2.0.19/src/Resources/functions/global.php

    r3350963 r3378632  
    1616function koko_analyics_tracking_script(): void
    1717{
    18     $script_loader = new KokoAnalytics\Script_Loader();
    19     $script_loader->maybe_enqueue_script(true);
     18    KokoAnalytics\Script_Loader::maybe_print_script();
    2019}
    2120
  • koko-analytics/tags/2.0.19/src/Resources/views/dashboard-page.php

    r3366958 r3378632  
    99/**
    1010 * @var \KokoAnalytics\Dashboard $this
    11  * @var \DateTimeInterface $dateStart
    12  * @var \DateTimeInterface $dateEnd
     11 * @var \DateTimeInterface $date_start
     12 * @var \DateTimeInterface $date_end
    1313 * @var object $totals
    1414 * @var int $realtime
    15  * @var string $dateFormat
     15 * @var string $date_format
    1616 * @var string $dashboard_url
    1717 * @var \KokoAnalytics\Dates $dates
    1818 * @var \KokoAnalytics\Stats $stats
     19 * @var array $next_dates
     20 * @var array $prev_dates
    1921 */
    2022
     
    3234                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar3 me-2" style="vertical-align: middle;" viewBox="0 0 16 16"><path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857z"/>
    3335  <path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
    34                     <?php echo wp_date($dateFormat, $dateStart->getTimestamp()); ?> — <?php echo wp_date($dateFormat, $dateEnd->getTimestamp()); ?>
     36                    <?php echo wp_date($date_format, $date_start->getTimestamp()); ?> — <?php echo wp_date($date_format, $date_end->getTimestamp()); ?>
    3537                </div>
    3638
     
    3840                    <div class="mb-3 bg-dark text-white p-3 rounded-top fw-bold d-flex justify-content-between">
    3941                        <?php // only output pagination for date ranges between reasonable dates... to prevent ever-crawling bots from going wild ?>
    40                         <?php if ($dateStart >  $total_start_date) { ?>
    41                         <a class="js-quicknav-prev text-decoration-none text-white me-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $prevDates[0]->format('Y-m-d'), 'end_date' => $prevDates[1]->format('Y-m-d')], $dashboard_url)); ?>">◂</a>
     42                        <?php if ($date_start >  $total_start_date) { ?>
     43                        <a class="js-quicknav-prev text-decoration-none text-white me-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $prev_dates[0]->format('Y-m-d'), 'end_date' => $prev_dates[1]->format('Y-m-d')], $dashboard_url)); ?>">◂</a>
    4244                        <?php } else { ?>
    4345                            <a class="text-decoration-none text-white me-2">◂</a>
    4446                        <?php } ?>
    45                         <span><?php echo wp_date($dateFormat, $dateStart->getTimestamp()); ?> — <?php echo wp_date($dateFormat, $dateEnd->getTimestamp()); ?></span>
    46                         <?php if ($dateEnd < $total_end_date) { ?>
    47                         <a class="js-quicknav-next text-decoration-none text-white ms-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $nextDates[0]->format('Y-m-d'), 'end_date' => $nextDates[1]->format('Y-m-d')], $dashboard_url)); ?>">▸</a>
     47                        <span><?php echo wp_date($date_format, $date_start->getTimestamp()); ?> — <?php echo wp_date($date_format, $date_end->getTimestamp()); ?></span>
     48                        <?php if ($date_end < $total_end_date) { ?>
     49                        <a class="js-quicknav-next text-decoration-none text-white ms-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $next_dates[0]->format('Y-m-d'), 'end_date' => $next_dates[1]->format('Y-m-d')], $dashboard_url)); ?>">▸</a>
    4850                        <?php } else { ?>
    4951                            <a class="text-decoration-none text-white ms-2">▸</a>
     
    6971                            <label for='ka-date-start' class="ka-label"><?php esc_html_e('Start date', 'koko-analytics'); ?></label>
    7072                            <input name="start_date" id='ka-date-start' type="date" size="10" min="2000-01-01" max="2100-01-01"
    71                                    value="<?php echo $dateStart->format('Y-m-d'); ?>" class="ka-input">
     73                                   value="<?php echo $date_start->format('Y-m-d'); ?>" class="ka-input">
    7274                        </div>
    7375                        <div class="mb-3">
    7476                            <label for='ka-date-end' class="ka-label"><?php esc_html_e('End date', 'koko-analytics'); ?></label>
    7577                            <input name="end_date" id='ka-date-end' type="date" size="10" min="2000-01-01" max="2100-01-01"
    76                                    value="<?php echo $dateEnd->format('Y-m-d'); ?>" class="ka-input">
     78                                   value="<?php echo $date_end->format('Y-m-d'); ?>" class="ka-input">
    7779                        </div>
    7880                        <div>
     
    8991            </div>
    9092
    91             <?php do_action('koko_analytics_after_datepicker', $dateStart, $dateEnd); ?>
     93            <?php do_action('koko_analytics_after_datepicker', $date_start, $date_end); ?>
    9294        </div>
    9395
     
    168170    <?php if (count($chart_data) > 1) { ?>
    169171    <div class="ka-box mb-3 p-3">
    170         <?php new Chart_View($chart_data, $dateStart, $dateEnd); ?>
     172        <?php new Chart_View($chart_data, $date_start, $date_end); ?>
    171173    </div>
    172174    <?php } ?>
     
    262264
    263265        <?php do_action_deprecated('koko_analytics_show_dashboard_components', [], '1.4', 'koko_analytics_after_dashboard_components'); ?>
    264         <?php do_action('koko_analytics_after_dashboard_components', $dateStart, $dateEnd); ?>
     266        <?php do_action('koko_analytics_after_dashboard_components', $date_start, $date_end); ?>
    265267    </div><?php // end div.ka-row ?>
    266268
  • koko-analytics/tags/2.0.19/src/Resources/views/dashboard-widget.php

    r3352477 r3378632  
    88* @var array $referrers
    99* @var stdClass $totals
    10 * @var \DateTimeInterface $dateStart
    11 * @var \DateTimeInterface $dateEnd
     10* @var \DateTimeInterface $today
     11* @var \DateTimeInterface $date_start
     12* @var \DateTimeInterface $date_end
    1213*/
    1314
     
    3132           <?php esc_html_e('Showing site visits over last 14 days', 'koko-analytics'); ?>
    3233        </h3>
    33         <div class="">
    34         <?php new Chart_View($chart_data, $dateStart, $dateEnd, 200, false); ?>
     34        <div>
     35        <?php new Chart_View($chart_data, $date_start, $date_end, 200, false); ?>
    3536        </div>
    3637    </div>
  • koko-analytics/tags/2.0.19/src/Resources/views/nav.php

    r3350963 r3378632  
    11<?php
     2
    23/**
    34* @var string $tab
    45*/
     6
     7use KokoAnalytics\Router;
     8
    59?>
    610<?php if (current_user_can('manage_koko_analytics')) { ?>
     
    812    <?php if (current_user_can('view_koko_analytics')) : ?>
    913    <ul class="list-inline m-0">
    10         <li class="list-inline-item m-0 ms-2"><a href="<?php echo esc_attr(add_query_arg(['koko-analytics-dashboard' => 1], home_url())); ?>" <?php echo isset($_GET['koko-analytics-dashboard']) ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard (full screen)', 'koko-analytics'); ?></a></li>
     14        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('dashboard-standalone'); ?>" <?= Router::is('dashboard-standalone') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard (full screen)', 'koko-analytics'); ?></a></li>
    1115
    12         <li class="list-inline-item m-0 ms-2"><a href="<?php echo admin_url('index.php?page=koko-analytics'); ?>" <?php echo $tab === 'dashboard' && !isset($_GET['koko-analytics-dashboard']) ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard', 'koko-analytics'); ?></a></li>
     16        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('dashboard-embedded') ?>" <?= Router::is('dashboard-embedded') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard', 'koko-analytics'); ?></a></li>
    1317    <?php endif; ?>
    1418        <?php if (current_user_can('manage_koko_analytics')) : ?>
    15         <li class="list-inline-item m-0 ms-2"><a href="<?php echo admin_url('index.php?page=koko-analytics&tab=settings'); ?>" <?php echo $tab === 'settings' ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Settings', 'koko-analytics'); ?></a></li>
     19        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('settings-page') ?>" <?= Router::is('settings-page') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Settings', 'koko-analytics'); ?></a></li>
    1620        <?php endif; ?>
    1721    </ul>
  • koko-analytics/tags/2.0.19/src/Resources/views/settings-page.php

    r3366958 r3378632  
    22
    33use KokoAnalytics\Endpoint_Installer;
     4use KokoAnalytics\Router;
    45
    56 defined('ABSPATH') or exit;
     
    1415 */
    1516$tab          = 'settings';
    16 $public_dashboard_url = add_query_arg(['koko-analytics-dashboard' => 1], home_url());
     17$public_dashboard_url = Router::url('dashboard-standalone');
    1718?>
    1819<div class="wrap koko-analytics" id="koko-analytics-admin">
     
    238239                <ul class="ul-square">
    239240                    <li><a href="<?php echo esc_attr(add_query_arg(['tab' => 'jetpack_importer'])); ?>"><?php esc_html_e('Import from Jetpack Stats', 'koko-analytics'); ?></a></li>
     241                    <li><a href="<?php echo esc_attr(add_query_arg(['tab' => 'plausible_importer'])); ?>"><?php esc_html_e('Import from Plausible', 'koko-analytics'); ?></a></li>
    240242                </ul>
    241243            </div>
     
    262264                } else {
    263265                    // store empty array to prevent doing an HTTP request on every page load
    264                     // we'll try again in 8 hours
     266                    // we'll try again in 24 hours
    265267                    $posts = [];
    266268                }
    267                 set_transient('koko_analytics_remote_posts', $posts, HOUR_IN_SECONDS * 8);
     269                set_transient('koko_analytics_remote_posts', $posts, HOUR_IN_SECONDS * 24);
    268270            }
    269271
     
    283285
    284286<?php if (isset($_GET['notice'])) { ?>
    285 <script>history.replaceState({}, null, '<?= admin_url('index.php?page=koko-analytics&tab=settings'); ?>');</script>
     287<script>history.replaceState({}, null, "<?= Router::url('settings-page'); ?>");</script>
    286288<?php } ?>
  • koko-analytics/tags/2.0.19/src/Resources/views/standalone.php

    r3350963 r3378632  
    1111    <meta name="viewport" content="width=device-width, initial-scale=1">
    1212    <meta name="referrer" content="no-referrer-when-downgrade">
    13     <meta name="robots" content="noindex,nofollow">
    1413    <title>Koko Analytics</title>
    1514    <meta name="apple-mobile-web-app-capable" content="yes">
     
    1918    <link rel="manifest" href="<?php echo plugins_url('assets/dist/manifest.json', KOKO_ANALYTICS_PLUGIN_FILE); ?>">
    2019    <link rel="shortcut icon" href="<?php echo plugins_url('assets/dist/img/favicon.ico', KOKO_ANALYTICS_PLUGIN_FILE); ?>">
     20    <link rel="canonical" href="<?= site_url('?koko-analytics-dashboard'); ?>">
     21    <meta name="robots" content="nofollow, noindex">
    2122    <meta name="theme-color" content="#B60205">
    2223</head>
    2324<body class="koko-analytics">
    24     <?php $this->show(); ?>
     25    <?php parent::show(); ?>
    2526    <script>
    2627    if ('serviceWorker' in navigator) {
  • koko-analytics/tags/2.0.19/src/Rest.php

    r3350963 r3378632  
    88
    99namespace KokoAnalytics;
     10
     11use DateTimeImmutable;
    1012
    1113class Rest
     
    129131    public function get_stats(\WP_REST_Request $request): \WP_REST_Response
    130132    {
     133        $timezone = wp_timezone();
    131134        $params             = $request->get_query_params();
    132         $start_date         = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    133         $end_date           = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     135        $start_date         = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     136        $end_date           = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    134137        $group = ($params['monthly'] ?? false) ? 'month' : 'day';
    135138        $page = $params['page'] ?? 0;
     
    143146    public function get_totals(\WP_REST_Request $request): \WP_REST_Response
    144147    {
     148        $timezone = wp_timezone();
    145149        $params     = $request->get_query_params();
    146         $start_date = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    147         $end_date   = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     150        $start_date = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     151        $end_date   = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    148152        $page = $params['page'] ?? 0;
    149153        $result = (new Stats())->get_totals($start_date, $end_date, $page);
     
    156160    public function get_posts(\WP_REST_Request $request): \WP_REST_Response
    157161    {
     162        $timezone = wp_timezone();
    158163        $params     = $request->get_query_params();
    159         $start_date = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    160         $end_date   = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     164        $start_date = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     165        $end_date   = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    161166        $offset     = isset($params['offset']) ? absint($params['offset']) : 0;
    162167        $limit      = isset($params['limit']) ? absint($params['limit']) : 10;
     
    170175    public function get_referrers(\WP_REST_Request $request): \WP_REST_Response
    171176    {
     177        $timezone = wp_timezone();
    172178        $params             = $request->get_query_params();
    173         $start_date         = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    174         $end_date           = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     179        $start_date         = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     180        $end_date           = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    175181        $offset             = isset($params['offset']) ? absint($params['offset']) : 0;
    176182        $limit              = isset($params['limit']) ? absint($params['limit']) : 10;
  • koko-analytics/tags/2.0.19/src/Script_Loader.php

    r3350963 r3378632  
    1010
    1111use KokoAnalytics\Normalizers\Normalizer;
    12 use WP_User;
    1312
    1413class Script_Loader
    1514{
    16     /**
    17      * @param bool $echo Whether to use the default WP script enqueue method or print the script tag directly
    18      */
    19     public static function maybe_enqueue_script(bool $echo = false): void
     15    public static function maybe_print_script(): void
    2016    {
    2117        $load_script = apply_filters('koko_analytics_load_tracking_script', true);
     
    2420        }
    2521
    26         if (is_request_excluded()) {
     22        if (is_request_excluded() || is_preview()) {
    2723            return;
    2824        }
    2925
    30         // TODO: Handle "term" requests so we track both terms and post types.
    31         add_filter('script_loader_tag', [ Script_Loader::class , 'add_async_attribute' ], 20, 2);
    32 
    33         if (false === $echo) {
    34             // Print configuration object early on in the HTML so scripts can modify it
    35             if (did_action('wp_head')) {
    36                 self::print_js_object();
    37             } else {
    38                 add_action('wp_head', [ Script_Loader::class , 'print_js_object' ], 1, 0);
    39             }
    40 
    41             // Enqueue the actual tracking script (in footer, if possible)
    42             wp_enqueue_script('koko-analytics', plugins_url('assets/dist/js/script.js', KOKO_ANALYTICS_PLUGIN_FILE), [], KOKO_ANALYTICS_VERSION, true);
    43         } else {
    44             self::print_js_object();
    45             echo '<script defer src="', plugins_url('assets/dist/js/script.js?ver=' . KOKO_ANALYTICS_VERSION, KOKO_ANALYTICS_PLUGIN_FILE), '"></script>';
    46         }
     26        echo PHP_EOL . '<!-- Koko Analytics v' . KOKO_ANALYTICS_VERSION . ' - https://www.kokoanalytics.com/ -->' . PHP_EOL;
     27        wp_print_inline_script_tag(file_get_contents(KOKO_ANALYTICS_PLUGIN_DIR . '/assets/dist/js/script.js'));
     28        echo PHP_EOL;
    4729    }
    4830
     
    6345    private static function get_tracker_url(): string
    6446    {
    65         global $wp;
    66 
    6747        // People can create their own endpoint and define it through this constant
    6848        if (\defined('KOKO_ANALYTICS_CUSTOM_ENDPOINT') && KOKO_ANALYTICS_CUSTOM_ENDPOINT) {
     
    8060    public static function get_request_path(): string
    8161    {
    82         $path = trim($_SERVER["REQUEST_URI"] ?? '');
    83         return Normalizer::path($path);
     62        return Normalizer::path(trim($_SERVER["REQUEST_URI"] ?? ''));
    8463    }
    8564
     
    132111        echo '<amp-analytics><script type="application/json">', json_encode($config), '</script></amp-analytics>';
    133112    }
    134 
    135     /**
    136      * @param string $tag
    137      * @param string $handle
    138      */
    139     public static function add_async_attribute($tag, $handle)
    140     {
    141         if ($handle !== 'koko-analytics' || strpos($tag, ' defer') !== false) {
    142             return $tag;
    143         }
    144 
    145         return str_replace(' src=', ' defer src=', $tag);
    146     }
    147113}
  • koko-analytics/tags/2.0.19/src/Shortcodes/Shortcode_Site_Counter.php

    r3364374 r3378632  
    2020use KokoAnalytics\Normalizers\Normalizer;
    2121use KokoAnalytics\Stats;
    22 
    23 use function KokoAnalytics\create_local_datetime;
    2422
    2523class Shortcode_Site_Counter
  • koko-analytics/tags/2.0.19/src/Stats.php

    r3366958 r3378632  
    2828    public function get_totals(string $start_date, string $end_date, $page = 0, $unused = null): object
    2929    {
    30         /** @var wpdb $wpdb */
     30        /** @var \wpdb $wpdb */
    3131        global $wpdb;
    3232
     
    7878    public function get_stats(string $start_date, string $end_date, string $group = 'day', $page = ''): array
    7979    {
    80         /** @var wpdb $wpdb */
     80        /** @var \wpdb $wpdb */
    8181        global $wpdb;
    8282
     
    121121    public function get_posts(string $start_date, string $end_date, int $offset = 0, int $limit = 10): array
    122122    {
    123         /** @var wpdb $wpdb */
     123        /** @var \wpdb $wpdb */
    124124        global $wpdb;
    125125
     
    128128                FROM {$wpdb->prefix}koko_analytics_post_stats s
    129129                JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id
    130                 LEFT JOIN {$wpdb->prefix}posts wp ON s.post_id = wp.ID
     130                LEFT JOIN {$wpdb->prefix}posts wp ON wp.ID = s.post_id
    131131                WHERE s.date >= %s AND s.date <= %s
    132132                GROUP BY p.path, s.post_id
     
    138138        return array_map(function ($row) {
    139139            $row->pageviews = (int) $row->pageviews;
    140             $row->visitors = (int) $row->visitors;
     140            $row->visitors = max(1, (int) $row->visitors);
    141141
    142142            // for backwards compatibility with versions before 2.0
     
    151151    public function count_posts(string $start_date, string $end_date): int
    152152    {
    153         /** @var wpdb $wpdb */
     153        /** @var \wpdb $wpdb */
    154154        global $wpdb;
    155155        return (int) $wpdb->get_var($wpdb->prepare(
     
    169169    public function get_referrers(string $start_date, string $end_date, int $offset = 0, int $limit = 10): array
    170170    {
    171         /** @var wpdb $wpdb */
    172         global $wpdb;
    173         return $wpdb->get_results($wpdb->prepare(
     171        /** @var \wpdb $wpdb */
     172        global $wpdb;
     173
     174        return array_map(function ($row) {
     175            $row->pageviews = (int) $row->pageviews;
     176            $row->visitors = max(1, (int) $row->visitors);
     177            return $row;
     178        }, $wpdb->get_results($wpdb->prepare(
    174179            "SELECT s.id, url, SUM(visitors) As visitors, SUM(pageviews) AS pageviews
    175180                FROM {$wpdb->prefix}koko_analytics_referrer_stats s
     
    180185                LIMIT %d, %d",
    181186            [$start_date, $end_date, $offset, $limit]
    182         ));
     187        )));
    183188    }
    184189
    185190    public function count_referrers(string $start_date, string $end_date): int
    186191    {
    187         /** @var wpdb $wpdb */
     192        /** @var \wpdb $wpdb */
    188193        global $wpdb;
    189194        return (int) $wpdb->get_var($wpdb->prepare(
  • koko-analytics/trunk/CHANGELOG.md

    r3366958 r3378632  
    11# Changelog
     2
     3### 2.0.19 - Oct 15, 2025
     4
     5- Print (< 500 bytes) tracking script inline in page HTML to save on an additional HTTP request and resolve overly aggressive cache issues.
     6- Add importer for Plausible.
     7- Change public dashboard URL to `/koko-analytics-dashboard/` if pretty permalinks are enabled.
     8- Exclude visits to post previews.
     9
    210
    311### 2.0.18 - Sep 24, 2025
  • koko-analytics/trunk/assets/dist/js/script.js

    r3362953 r3378632  
    1 /*! For license information please see script.js.LICENSE.txt */
    2 !function(){var e=window,t="koko_analytics";e[t].trackPageview=function(){if("prerender"!=document.visibilityState&&!/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview/i.test(navigator.userAgent)){var a=e[t].path;a||(a=window.location.pathname+window.location.search);var o,r=0==document.referrer.indexOf(e[t].site_url)?"":document.referrer;o={pa:a,po:e[t].post_id,r:r},e[t].use_cookie?o.m="c":e[t].method&&(o.m=e[t].method[0]),navigator.sendBeacon(e[t].url,new URLSearchParams(o))}},e.addEventListener("load",function(){e[t].trackPageview()})}();
     1!function(){var e=window,r="koko_analytics";function t(t){t.m=e[r].use_cookie?"c":e[r].method[0],navigator.sendBeacon(e[r].url,new URLSearchParams(t))}e[r].request=t,e[r].trackPageview=function(){if("prerender"!=document.visibilityState&&!/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview/i.test(navigator.userAgent)){var i=0==document.referrer.indexOf(e[r].site_url)?"":document.referrer;t({pa:e[r].path,po:e[r].post_id,r:i})}},e.addEventListener("load",function(){e[r].trackPageview()})}();
  • koko-analytics/trunk/data/referrer-blocklist

    r3359615 r3378632  
    841841getrichquick.ml
    842842getrichquickly.info
     843gevciamst.online
    843844gezlev.com.ua
    844845ghazel.ru
     
    15071508pfrf-kabinet.ru
    15081509pharm--shop.ru
     1510phimarshcer.online
    15091511phimmakinhdi.com
    15101512photo-clip.ru
  • koko-analytics/trunk/koko-analytics.php

    r3366958 r3378632  
    44Plugin Name: Koko Analytics
    55Plugin URI: https://www.kokoanalytics.com/#utm_source=wp-plugin&utm_medium=koko-analytics&utm_campaign=plugins-page
    6 Version: 2.0.18
     6Version: 2.0.19
    77Description: Privacy-friendly and efficient statistics for your WordPress site.
    88Author: ibericode
     
    3939use KokoAnalytics\Widgets\Most_Viewed_Posts_Widget;
    4040
    41 \define('KOKO_ANALYTICS_VERSION', '2.0.18');
     41\define('KOKO_ANALYTICS_VERSION', '2.0.19');
    4242\define('KOKO_ANALYTICS_PLUGIN_FILE', __FILE__);
    4343\define('KOKO_ANALYTICS_PLUGIN_DIR', __DIR__);
     
    7272
    7373// script loader
    74 add_action('wp_enqueue_scripts', [Script_Loader::class, 'maybe_enqueue_script'], 10, 0);
     74add_action('wp_head', [ Script_Loader::class , 'print_js_object' ], 1, 0);
     75add_action('wp_footer', [Script_Loader::class, 'maybe_print_script'], 10, 0);
    7576add_action('amp_print_analytics', [Script_Loader::class, 'print_amp_analytics_tag'], 10, 0);
    7677add_action('admin_bar_menu', [Admin\Bar::class, 'register'], 40, 1);
     
    108109// maybe show standalone dashboard
    109110add_action('wp', function () {
    110     if (!isset($_GET['koko-analytics-dashboard'])) {
     111    if (!Router::is('dashboard-standalone')) {
    111112        return;
    112113    }
     
    122123    }
    123124
    124     (new Dashboard())->show_standalone_dashboard_page();
     125    (new Dashboard_Standalone())->show();
     126    exit;
    125127}, 10, 0);
    126128
     
    134136}
    135137
    136 // on plugin update (but using old code)
    137 // this breaks in 2.x because of the new file structure
    138 // TODO: Reactivate once 2.x stabilises
    139 // add_filter('upgrader_process_complete', function () {
    140 //     do_action('koko_analytics_aggregate_stats');
    141 // });
     138// on plugin update (but using old code that's already in memory)
     139add_filter('upgrader_process_complete', function () {
     140    do_action('koko_analytics_aggregate_stats');
     141});
    142142
    143143// on plugin activation
  • koko-analytics/trunk/readme.txt

    r3373525 r3378632  
    55Requires at least: 6.0
    66Tested up to: 6.8
    7 Stable tag: 2.0.18
     7Stable tag: 2.0.19
    88License: GPL-3.0-or-later
    99License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    132132== Changelog ==
    133133
     134### 2.0.19 - Oct 15, 2025
     135
     136- Print (< 500 bytes) tracking script inline in page HTML to save on an additional HTTP request and resolve overly aggressive cache issues.
     137- Add importer for Plausible.
     138- Change public dashboard URL to `/koko-analytics-dashboard/` if pretty permalinks are enabled.
     139- Exclude visits to post previews.
     140
     141
    134142### 2.0.18 - Sep 24, 2025
    135143
     
    444452#### 1.4.4 - Nov 4, 2024
    445453
    446 - Add Jetpack Stats importer to import your historical analytics data into Koko Analytics. Go to the settings page (with Jetpack still enabled) to access it.
    447 - Fix settings page showing proxy IP instead of client IP if using reverse proxy.
    448 - Fix use of PHP 7.4 only feature in thousands separator in source ...
    449 
     454- Add Jetpack Stats ...
     455
  • koko-analytics/trunk/src/Admin/Actions.php

    r3366958 r3378632  
    193193
    194194        do {
    195             $results = $wpdb->get_results($wpdb->prepare("SELECT post_id, path_id, p.path FROM {$wpdb->prefix}koko_analytics_post_stats s LEFT JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id WHERE post_id IS NOT NULL AND post_id != 0 AND date <= '2025-08-29' GROUP BY post_id LIMIT %d OFFSET %d", [$limit, $offset]));
     195            $results = $wpdb->get_results($wpdb->prepare("SELECT post_id, path_id, p.path FROM {$wpdb->prefix}koko_analytics_post_stats s LEFT JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id WHERE post_id IS NOT NULL AND post_id != 0 GROUP BY post_id LIMIT %d OFFSET %d", [$limit, $offset]));
    196196            $offset += $limit;
    197197            if (!$results) {
  • koko-analytics/trunk/src/Admin/Admin.php

    r3366958 r3378632  
    99namespace KokoAnalytics\Admin;
    1010
    11 use KokoAnalytics\Jetpack_Importer;
     11use KokoAnalytics\Import\Jetpack_Importer;
     12use KokoAnalytics\Import\Plausible_Importer;
     13use KokoAnalytics\Router;
    1214
    1315class Admin
     
    4143        add_action('koko_analytics_start_jetpack_import', [Jetpack_Importer::class, 'start_import'], 10, 0);
    4244        add_action('koko_analytics_jetpack_import_chunk', [Jetpack_Importer::class, 'import_chunk'], 10, 0);
     45
     46        // actions for plausible importer
     47        add_action('koko_analytics_show_plausible_importer_page', [Plausible_Importer::class, 'show_page'], 10, 0);
     48        add_action('koko_analytics_start_plausible_import', [Plausible_Importer::class, 'start_import'], 10, 0);
    4349    }
    4450
     
    5864    public function add_plugin_settings_link($links): array
    5965    {
    60         $href = admin_url('index.php?page=koko-analytics&tab=settings');
     66        $href = Router::url('settings-page');
    6167        $label = esc_html__('Settings', 'koko-analytics');
    6268        $settings_link = "<a href=\"{$href}\">{$label}</a>";
  • koko-analytics/trunk/src/Admin/Data_Export.php

    r3366958 r3378632  
    99namespace KokoAnalytics\Admin;
    1010
    11 use function KokoAnalytics\create_local_datetime;
     11use DateTimeImmutable;
    1212
    1313class Data_Export
     
    3636    {
    3737        // write to HTTP stream
    38         $date = create_local_datetime('now')->format("Y-m-d");
     38        $date = (new DateTimeImmutable('now', wp_timezone()))->format("Y-m-d");
    3939        $site_url = parse_url(get_site_url(), PHP_URL_HOST);
    4040
  • koko-analytics/trunk/src/Dashboard.php

    r3362953 r3378632  
    99namespace KokoAnalytics;
    1010
     11use DateTimeImmutable;
     12
    1113class Dashboard
    1214{
    13     public function show_standalone_dashboard_page(): void
    14     {
    15         require KOKO_ANALYTICS_PLUGIN_DIR . '/src/Resources/views/standalone.php';
    16         exit;
    17     }
    18 
    19     public function show(): void
     15    protected function get_base_url()
     16    {
     17        return Router::url('dashboard-embedded');
     18    }
     19
     20    public function show()
    2021    {
    2122        $settings   = get_settings();
    2223        $stats = new Stats();
    2324        $items_per_page = (int) apply_filters('koko_analytics_items_per_page', 20);
    24         $dateFormat = get_option('date_format', 'Y-m-d');
    25         $dashboard_url = remove_query_arg(['start_date', 'end_date', 'view', 'posts', 'referrers']);
     25        $date_format = get_option('date_format', 'Y-m-d');
     26        $dashboard_url = $this->get_base_url();
    2627
    2728        // parse query params
     
    3334            $range = $settings['default_view'];
    3435        }
    35         $now = create_local_datetime('now');
     36        $timezone = wp_timezone();
     37        $now = new DateTimeImmutable('now', $timezone);
    3638        $week_starts_on = (int) get_option('start_of_week', 0);
    37         $dateRange = $this->get_dates_for_range($now, $range, $week_starts_on);
     39        $date_range = $this->get_dates_for_range($now, $range, $week_starts_on);
    3840        $page = isset($_GET['p']) ? trim($_GET['p']) : 0;
    3941
    4042        try {
    41             $dateStart  = isset($_GET['start_date']) ? create_local_datetime($_GET['start_date']) : $dateRange[0];
     43            $date_start  = isset($_GET['start_date']) ? new DateTimeImmutable($_GET['start_date'], $timezone) : $date_range[0];
    4244        } catch (\Exception $e) {
    43             $dateStart = $dateRange[0];
     45            $date_start = $date_range[0];
    4446        }
    4547        try {
    46             $dateEnd    = isset($_GET['end_date']) ? create_local_datetime($_GET['end_date']) : $dateRange[1];
     48            $date_end    = isset($_GET['end_date']) ? new DateTimeImmutable($_GET['end_date'], $timezone) : $date_range[1];
    4749        } catch (\Exception $e) {
    48             $dateEnd = $dateRange[1];
     50            $date_end = $date_range[1];
    4951        }
    5052
     
    5759
    5860        // calculate next and previous dates for datepicker component and comparison
    59         $nextDates = $this->get_next_period($dateStart, $dateEnd, 1);
    60         $prevDates = $this->get_next_period($dateStart, $dateEnd, -1);
    61 
    62         $dateStartStr = $dateStart->format('Y-m-d');
    63         $dateEndStr = $dateEnd->format('Y-m-d');
    64 
    65         $totals = $stats->get_totals($dateStartStr, $dateEndStr, $page);
    66         $totals_previous = $stats->get_totals($prevDates[0]->format('Y-m-d'), $prevDates[2]->format('Y-m-d'), $page);
    67 
    68         $posts = $stats->get_posts($dateStartStr, $dateEndStr, $posts_offset, $posts_limit);
    69         $posts_count = $stats->count_posts($dateStartStr, $dateEndStr);
    70         $referrers = $stats->get_referrers($dateStartStr, $dateEndStr, $referrers_offset, $referrers_limit);
    71         $referrers_count = $stats->count_referrers($dateStartStr, $dateEndStr);
     61        $next_dates = $this->get_next_period($date_start, $date_end, 1);
     62        $prev_dates = $this->get_next_period($date_start, $date_end, -1);
     63
     64        $date_start_str = $date_start->format('Y-m-d');
     65        $date_end_str = $date_end->format('Y-m-d');
     66
     67        $totals = $stats->get_totals($date_start_str, $date_end_str, $page);
     68        $totals_previous = $stats->get_totals($prev_dates[0]->format('Y-m-d'), $prev_dates[2]->format('Y-m-d'), $page);
     69
     70        $posts = $stats->get_posts($date_start_str, $date_end_str, $posts_offset, $posts_limit);
     71        $posts_count = $stats->count_posts($date_start_str, $date_end_str);
     72        $referrers = $stats->get_referrers($date_start_str, $date_end_str, $referrers_offset, $referrers_limit);
     73        $referrers_count = $stats->count_referrers($date_start_str, $date_end_str);
    7274        $realtime = get_realtime_pageview_count('-1 hour');
    7375
    7476        if (isset($_GET['group']) && in_array($_GET['group'], ['day', 'week', 'month'])) {
    75             $groupChartBy = $_GET['group'];
    76         } else {
    77             $groupChartBy = $dateEnd->getTimestamp() - $dateStart->getTimestamp() >= 86400 * 90 ? 'month' : 'day';
    78         }
    79         $chart_data =  $stats->get_stats($dateStartStr, $dateEndStr, $groupChartBy, $page);
     77            $group_chart_by = $_GET['group'];
     78        } else {
     79            $group_chart_by = $date_end->getTimestamp() - $date_start->getTimestamp() >= 86400 * 90 ? 'month' : 'day';
     80        }
     81        $chart_data =  $stats->get_stats($date_start_str, $date_end_str, $group_chart_by, $page);
    8082
    8183        require KOKO_ANALYTICS_PLUGIN_DIR . '/src/Resources/views/dashboard-page.php';
    8284    }
    8385
    84     public function get_next_period(\DateTimeImmutable $dateStart, \DateTimeImmutable $dateEnd, int $dir = 1): array
     86    public function get_next_period(\DateTimeImmutable $date_start, \DateTimeImmutable $date_end, int $dir = 1): array
    8587    {
    8688        $now = new \DateTimeImmutable('now', wp_timezone());
    8789        $modifier = $dir > 0 ? "+" : "-";
    8890
    89         if ($dateStart->format('d') === "01" && $dateEnd->format('d') === $dateEnd->format('t')) {
     91        if ($date_start->format('d') === "01" && $date_end->format('d') === $date_end->format('t')) {
    9092            // cycling full months
    91             $diffInMonths = 1 + ((int) $dateEnd->format('Y') - (int) $dateStart->format('Y')) * 12 + (int) $dateEnd->format('m') - (int) $dateStart->format('m');
    92             $periodStart = $dateStart->setDate((int) $dateStart->format('Y'), (int) $dateStart->format('m') + ($dir * $diffInMonths), 1);
    93             $periodEnd = $dateEnd->setDate((int) $dateStart->format('Y'), (int) $dateEnd->format('m') + ($dir * $diffInMonths), 5);
     93            $diffInMonths = 1 + ((int) $date_end->format('Y') - (int) $date_start->format('Y')) * 12 + (int) $date_end->format('m') - (int) $date_start->format('m');
     94            $periodStart = $date_start->setDate((int) $date_start->format('Y'), (int) $date_start->format('m') + ($dir * $diffInMonths), 1);
     95            $periodEnd = $date_end->setDate((int) $date_start->format('Y'), (int) $date_end->format('m') + ($dir * $diffInMonths), 5);
    9496            $periodEnd = $periodEnd->setDate((int) $periodEnd->format('Y'), (int) $periodEnd->format('m'), (int) $periodEnd->format('t'));
    9597        } else {
    96             $diffInDays = $dateEnd->diff($dateStart)->days + 1;
    97             $periodStart = $dateStart->modify("{$modifier}{$diffInDays} days");
    98             $periodEnd = $dateEnd->modify("{$modifier}{$diffInDays} days");
    99         }
    100 
    101         if ($dateEnd > $now) {
     98            $diffInDays = $date_end->diff($date_start)->days + 1;
     99            $periodStart = $date_start->modify("{$modifier}{$diffInDays} days");
     100            $periodEnd = $date_end->modify("{$modifier}{$diffInDays} days");
     101        }
     102
     103        if ($date_end > $now) {
    102104            // limit end date to difference between now and start date, counting from start date
    103             $days_diff = $now->diff($dateStart)->days;
     105            $days_diff = $now->diff($date_start)->days;
    104106            $compareEnd = $periodStart->modify("+{$days_diff} days");
    105107        } else {
    106108            $compareEnd = $periodEnd;
    107109        }
    108 
    109110
    110111        return [ $periodStart, $periodEnd, $compareEnd ];
  • koko-analytics/trunk/src/Dashboard_Widget.php

    r3350963 r3378632  
    88
    99namespace KokoAnalytics;
     10
     11use DateTimeImmutable;
    1012
    1113class Dashboard_Widget
     
    2729
    2830        $number_of_top_items = (int) apply_filters('koko_analytics_dashboard_widget_number_of_top_items', 5);
     31        $timezone = wp_timezone();
    2932        $stats = new Stats();
    30         $dateToday = create_local_datetime('today, midnight')->format('Y-m-d');
    31         $totals = $stats->get_totals($dateToday, $dateToday);
     33        $today = (new DateTimeImmutable('today, midnight', $timezone))->format('Y-m-d');
     34        $totals = $stats->get_totals($today, $today);
    3235
    3336        // get realtime pageviews, but limit it to number of total pageviews today in case viewing shortly after midnight
    3437        $realtime = min($totals->pageviews, get_realtime_pageview_count('-1 hour'));
    3538
    36         $dateStart = create_local_datetime('-14 days');
    37         $dateEnd = create_local_datetime('now');
    38         $chart_data = $stats->get_stats($dateStart->format('Y-m-d'), $dateEnd->format('Y-m-d'), 'day');
     39        // get chart data
     40        $date_start = new DateTimeImmutable('-14 days', $timezone);
     41        $date_end = new DateTimeImmutable('now', $timezone);
     42        $chart_data = $stats->get_stats($date_start->format('Y-m-d'), $date_end->format('Y-m-d'), 'day');
    3943
    4044        if ($number_of_top_items > 0) {
    41             $posts = $stats->get_posts($dateToday, $dateToday, 0, $number_of_top_items);
    42             $referrers = $stats->get_referrers($dateToday, $dateToday, 0, $number_of_top_items);
     45            $posts = $stats->get_posts($today, $today, 0, $number_of_top_items);
     46            $referrers = $stats->get_referrers($today, $today, 0, $number_of_top_items);
    4347        }
    4448
  • koko-analytics/trunk/src/Pageview_Aggregator.php

    r3352477 r3378632  
    124124        global $wpdb;
    125125
    126         // insert referrer stats
     126        // insert page-specific stats
    127127        foreach ($this->post_stats as $date => $stats) {
    128             $paths = array_keys($stats);
    129             $path_map = Path_Repository::upsert($paths);
    130 
    131             // insert referrer stats
     128            $path_ids = Path_Repository::upsert(array_keys($stats));
    132129            $values = [];
    133130            foreach ($stats as $path => $r) {
    134                 array_push($values, $date, $path_map[$path], $r['post_id'], $r['visitors'], $r['pageviews']);
     131                array_push($values, $date, $path_ids[$path], $r['post_id'], $r['visitors'], $r['pageviews']);
    135132            }
    136133            $placeholders = rtrim(str_repeat('(%s,%d,%d,%d,%d),', count($stats)), ',');
     
    147144        // insert referrer stats
    148145        foreach ($this->referrer_stats as $date => $stats) {
    149             // retrieve ID's for known referrer urls
    150             $referrer_urls = array_keys($stats);
    151             $placeholders  = rtrim(str_repeat('%s,', count($referrer_urls)), ',');
    152             $results       = $wpdb->get_results($wpdb->prepare("SELECT id, url FROM {$wpdb->prefix}koko_analytics_referrer_urls r WHERE r.url IN({$placeholders})", $referrer_urls));
    153             foreach ($results as $r) {
    154                 $stats[ $r->url ]['id'] = $r->id;
    155             }
    156 
    157             // build query for new referrer urls
    158             $new_referrer_urls = [];
     146            $referrer_ids = Referrer_Repository::upsert(array_keys($stats));
     147            $values = [];
    159148            foreach ($stats as $url => $r) {
    160                 if (! isset($r['id'])) {
    161                     $new_referrer_urls[] = $url;
    162                 }
    163             }
    164 
    165             // insert new referrer urls and set ID in map
    166             if (count($new_referrer_urls) > 0) {
    167                 $values       = $new_referrer_urls;
    168                 $placeholders = rtrim(str_repeat('(%s),', count($values)), ',');
    169                 $wpdb->query($wpdb->prepare("INSERT INTO {$wpdb->prefix}koko_analytics_referrer_urls(url) VALUES {$placeholders}", $values));
    170                 $last_insert_id = $wpdb->insert_id;
    171                 foreach ($values as $url) {
    172                     $stats[ $url ]['id'] = $last_insert_id++;
    173                 }
    174             }
    175 
    176             // insert referrer stats
    177             $values = [];
    178             foreach ($stats as $r) {
    179                 array_push($values, $date, $r['id'], $r['visitors'], $r['pageviews']);
     149                array_push($values, $date, $referrer_ids[$url], $r['visitors'], $r['pageviews']);
    180150            }
    181151            $placeholders = rtrim(str_repeat('(%s,%d,%d,%d),', count($stats)), ',');
     
    192162        // remove all data older than 60 minutes
    193163        $one_hour_ago = \time() - 60 * 60;
    194         foreach ($counts as $timestamp => $v) {
     164        foreach ($counts as $timestamp => $unused) {
    195165            // delete all data older than one hour
    196166            if ((int) $timestamp < $one_hour_ago) {
  • koko-analytics/trunk/src/Resources/functions/functions.php

    r3352698 r3378632  
    121121function get_realtime_pageview_count($since = null): int
    122122{
    123     if (is_numeric($since) || is_int($since)) {
     123    if (is_numeric($since)) {
    124124        $since = (int) $since;
    125     } elseif (is_string($since)) {
    126         // $since is relative time string
    127         $since = strtotime($since);
    128125    } else {
    129         $since = strtotime('-5 minutes');
     126        $since = strtotime($since ?? '-5 minutes');
    130127    }
    131128
  • koko-analytics/trunk/src/Resources/functions/global.php

    r3350963 r3378632  
    1616function koko_analyics_tracking_script(): void
    1717{
    18     $script_loader = new KokoAnalytics\Script_Loader();
    19     $script_loader->maybe_enqueue_script(true);
     18    KokoAnalytics\Script_Loader::maybe_print_script();
    2019}
    2120
  • koko-analytics/trunk/src/Resources/views/dashboard-page.php

    r3366958 r3378632  
    99/**
    1010 * @var \KokoAnalytics\Dashboard $this
    11  * @var \DateTimeInterface $dateStart
    12  * @var \DateTimeInterface $dateEnd
     11 * @var \DateTimeInterface $date_start
     12 * @var \DateTimeInterface $date_end
    1313 * @var object $totals
    1414 * @var int $realtime
    15  * @var string $dateFormat
     15 * @var string $date_format
    1616 * @var string $dashboard_url
    1717 * @var \KokoAnalytics\Dates $dates
    1818 * @var \KokoAnalytics\Stats $stats
     19 * @var array $next_dates
     20 * @var array $prev_dates
    1921 */
    2022
     
    3234                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar3 me-2" style="vertical-align: middle;" viewBox="0 0 16 16"><path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857z"/>
    3335  <path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
    34                     <?php echo wp_date($dateFormat, $dateStart->getTimestamp()); ?> — <?php echo wp_date($dateFormat, $dateEnd->getTimestamp()); ?>
     36                    <?php echo wp_date($date_format, $date_start->getTimestamp()); ?> — <?php echo wp_date($date_format, $date_end->getTimestamp()); ?>
    3537                </div>
    3638
     
    3840                    <div class="mb-3 bg-dark text-white p-3 rounded-top fw-bold d-flex justify-content-between">
    3941                        <?php // only output pagination for date ranges between reasonable dates... to prevent ever-crawling bots from going wild ?>
    40                         <?php if ($dateStart >  $total_start_date) { ?>
    41                         <a class="js-quicknav-prev text-decoration-none text-white me-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $prevDates[0]->format('Y-m-d'), 'end_date' => $prevDates[1]->format('Y-m-d')], $dashboard_url)); ?>">◂</a>
     42                        <?php if ($date_start >  $total_start_date) { ?>
     43                        <a class="js-quicknav-prev text-decoration-none text-white me-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $prev_dates[0]->format('Y-m-d'), 'end_date' => $prev_dates[1]->format('Y-m-d')], $dashboard_url)); ?>">◂</a>
    4244                        <?php } else { ?>
    4345                            <a class="text-decoration-none text-white me-2">◂</a>
    4446                        <?php } ?>
    45                         <span><?php echo wp_date($dateFormat, $dateStart->getTimestamp()); ?> — <?php echo wp_date($dateFormat, $dateEnd->getTimestamp()); ?></span>
    46                         <?php if ($dateEnd < $total_end_date) { ?>
    47                         <a class="js-quicknav-next text-decoration-none text-white ms-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $nextDates[0]->format('Y-m-d'), 'end_date' => $nextDates[1]->format('Y-m-d')], $dashboard_url)); ?>">▸</a>
     47                        <span><?php echo wp_date($date_format, $date_start->getTimestamp()); ?> — <?php echo wp_date($date_format, $date_end->getTimestamp()); ?></span>
     48                        <?php if ($date_end < $total_end_date) { ?>
     49                        <a class="js-quicknav-next text-decoration-none text-white ms-2" href="<?php echo esc_attr(add_query_arg(['start_date' => $next_dates[0]->format('Y-m-d'), 'end_date' => $next_dates[1]->format('Y-m-d')], $dashboard_url)); ?>">▸</a>
    4850                        <?php } else { ?>
    4951                            <a class="text-decoration-none text-white ms-2">▸</a>
     
    6971                            <label for='ka-date-start' class="ka-label"><?php esc_html_e('Start date', 'koko-analytics'); ?></label>
    7072                            <input name="start_date" id='ka-date-start' type="date" size="10" min="2000-01-01" max="2100-01-01"
    71                                    value="<?php echo $dateStart->format('Y-m-d'); ?>" class="ka-input">
     73                                   value="<?php echo $date_start->format('Y-m-d'); ?>" class="ka-input">
    7274                        </div>
    7375                        <div class="mb-3">
    7476                            <label for='ka-date-end' class="ka-label"><?php esc_html_e('End date', 'koko-analytics'); ?></label>
    7577                            <input name="end_date" id='ka-date-end' type="date" size="10" min="2000-01-01" max="2100-01-01"
    76                                    value="<?php echo $dateEnd->format('Y-m-d'); ?>" class="ka-input">
     78                                   value="<?php echo $date_end->format('Y-m-d'); ?>" class="ka-input">
    7779                        </div>
    7880                        <div>
     
    8991            </div>
    9092
    91             <?php do_action('koko_analytics_after_datepicker', $dateStart, $dateEnd); ?>
     93            <?php do_action('koko_analytics_after_datepicker', $date_start, $date_end); ?>
    9294        </div>
    9395
     
    168170    <?php if (count($chart_data) > 1) { ?>
    169171    <div class="ka-box mb-3 p-3">
    170         <?php new Chart_View($chart_data, $dateStart, $dateEnd); ?>
     172        <?php new Chart_View($chart_data, $date_start, $date_end); ?>
    171173    </div>
    172174    <?php } ?>
     
    262264
    263265        <?php do_action_deprecated('koko_analytics_show_dashboard_components', [], '1.4', 'koko_analytics_after_dashboard_components'); ?>
    264         <?php do_action('koko_analytics_after_dashboard_components', $dateStart, $dateEnd); ?>
     266        <?php do_action('koko_analytics_after_dashboard_components', $date_start, $date_end); ?>
    265267    </div><?php // end div.ka-row ?>
    266268
  • koko-analytics/trunk/src/Resources/views/dashboard-widget.php

    r3352477 r3378632  
    88* @var array $referrers
    99* @var stdClass $totals
    10 * @var \DateTimeInterface $dateStart
    11 * @var \DateTimeInterface $dateEnd
     10* @var \DateTimeInterface $today
     11* @var \DateTimeInterface $date_start
     12* @var \DateTimeInterface $date_end
    1213*/
    1314
     
    3132           <?php esc_html_e('Showing site visits over last 14 days', 'koko-analytics'); ?>
    3233        </h3>
    33         <div class="">
    34         <?php new Chart_View($chart_data, $dateStart, $dateEnd, 200, false); ?>
     34        <div>
     35        <?php new Chart_View($chart_data, $date_start, $date_end, 200, false); ?>
    3536        </div>
    3637    </div>
  • koko-analytics/trunk/src/Resources/views/nav.php

    r3350963 r3378632  
    11<?php
     2
    23/**
    34* @var string $tab
    45*/
     6
     7use KokoAnalytics\Router;
     8
    59?>
    610<?php if (current_user_can('manage_koko_analytics')) { ?>
     
    812    <?php if (current_user_can('view_koko_analytics')) : ?>
    913    <ul class="list-inline m-0">
    10         <li class="list-inline-item m-0 ms-2"><a href="<?php echo esc_attr(add_query_arg(['koko-analytics-dashboard' => 1], home_url())); ?>" <?php echo isset($_GET['koko-analytics-dashboard']) ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard (full screen)', 'koko-analytics'); ?></a></li>
     14        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('dashboard-standalone'); ?>" <?= Router::is('dashboard-standalone') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard (full screen)', 'koko-analytics'); ?></a></li>
    1115
    12         <li class="list-inline-item m-0 ms-2"><a href="<?php echo admin_url('index.php?page=koko-analytics'); ?>" <?php echo $tab === 'dashboard' && !isset($_GET['koko-analytics-dashboard']) ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard', 'koko-analytics'); ?></a></li>
     16        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('dashboard-embedded') ?>" <?= Router::is('dashboard-embedded') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Dashboard', 'koko-analytics'); ?></a></li>
    1317    <?php endif; ?>
    1418        <?php if (current_user_can('manage_koko_analytics')) : ?>
    15         <li class="list-inline-item m-0 ms-2"><a href="<?php echo admin_url('index.php?page=koko-analytics&tab=settings'); ?>" <?php echo $tab === 'settings' ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Settings', 'koko-analytics'); ?></a></li>
     19        <li class="list-inline-item m-0 ms-2"><a href="<?= Router::url('settings-page') ?>" <?= Router::is('settings-page') ? 'class="text-black" aria-current="page"' : ''; ?>><?php esc_html_e('Settings', 'koko-analytics'); ?></a></li>
    1620        <?php endif; ?>
    1721    </ul>
  • koko-analytics/trunk/src/Resources/views/settings-page.php

    r3366958 r3378632  
    22
    33use KokoAnalytics\Endpoint_Installer;
     4use KokoAnalytics\Router;
    45
    56 defined('ABSPATH') or exit;
     
    1415 */
    1516$tab          = 'settings';
    16 $public_dashboard_url = add_query_arg(['koko-analytics-dashboard' => 1], home_url());
     17$public_dashboard_url = Router::url('dashboard-standalone');
    1718?>
    1819<div class="wrap koko-analytics" id="koko-analytics-admin">
     
    238239                <ul class="ul-square">
    239240                    <li><a href="<?php echo esc_attr(add_query_arg(['tab' => 'jetpack_importer'])); ?>"><?php esc_html_e('Import from Jetpack Stats', 'koko-analytics'); ?></a></li>
     241                    <li><a href="<?php echo esc_attr(add_query_arg(['tab' => 'plausible_importer'])); ?>"><?php esc_html_e('Import from Plausible', 'koko-analytics'); ?></a></li>
    240242                </ul>
    241243            </div>
     
    262264                } else {
    263265                    // store empty array to prevent doing an HTTP request on every page load
    264                     // we'll try again in 8 hours
     266                    // we'll try again in 24 hours
    265267                    $posts = [];
    266268                }
    267                 set_transient('koko_analytics_remote_posts', $posts, HOUR_IN_SECONDS * 8);
     269                set_transient('koko_analytics_remote_posts', $posts, HOUR_IN_SECONDS * 24);
    268270            }
    269271
     
    283285
    284286<?php if (isset($_GET['notice'])) { ?>
    285 <script>history.replaceState({}, null, '<?= admin_url('index.php?page=koko-analytics&tab=settings'); ?>');</script>
     287<script>history.replaceState({}, null, "<?= Router::url('settings-page'); ?>");</script>
    286288<?php } ?>
  • koko-analytics/trunk/src/Resources/views/standalone.php

    r3350963 r3378632  
    1111    <meta name="viewport" content="width=device-width, initial-scale=1">
    1212    <meta name="referrer" content="no-referrer-when-downgrade">
    13     <meta name="robots" content="noindex,nofollow">
    1413    <title>Koko Analytics</title>
    1514    <meta name="apple-mobile-web-app-capable" content="yes">
     
    1918    <link rel="manifest" href="<?php echo plugins_url('assets/dist/manifest.json', KOKO_ANALYTICS_PLUGIN_FILE); ?>">
    2019    <link rel="shortcut icon" href="<?php echo plugins_url('assets/dist/img/favicon.ico', KOKO_ANALYTICS_PLUGIN_FILE); ?>">
     20    <link rel="canonical" href="<?= site_url('?koko-analytics-dashboard'); ?>">
     21    <meta name="robots" content="nofollow, noindex">
    2122    <meta name="theme-color" content="#B60205">
    2223</head>
    2324<body class="koko-analytics">
    24     <?php $this->show(); ?>
     25    <?php parent::show(); ?>
    2526    <script>
    2627    if ('serviceWorker' in navigator) {
  • koko-analytics/trunk/src/Rest.php

    r3350963 r3378632  
    88
    99namespace KokoAnalytics;
     10
     11use DateTimeImmutable;
    1012
    1113class Rest
     
    129131    public function get_stats(\WP_REST_Request $request): \WP_REST_Response
    130132    {
     133        $timezone = wp_timezone();
    131134        $params             = $request->get_query_params();
    132         $start_date         = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    133         $end_date           = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     135        $start_date         = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     136        $end_date           = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    134137        $group = ($params['monthly'] ?? false) ? 'month' : 'day';
    135138        $page = $params['page'] ?? 0;
     
    143146    public function get_totals(\WP_REST_Request $request): \WP_REST_Response
    144147    {
     148        $timezone = wp_timezone();
    145149        $params     = $request->get_query_params();
    146         $start_date = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    147         $end_date   = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     150        $start_date = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     151        $end_date   = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    148152        $page = $params['page'] ?? 0;
    149153        $result = (new Stats())->get_totals($start_date, $end_date, $page);
     
    156160    public function get_posts(\WP_REST_Request $request): \WP_REST_Response
    157161    {
     162        $timezone = wp_timezone();
    158163        $params     = $request->get_query_params();
    159         $start_date = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    160         $end_date   = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     164        $start_date = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     165        $end_date   = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    161166        $offset     = isset($params['offset']) ? absint($params['offset']) : 0;
    162167        $limit      = isset($params['limit']) ? absint($params['limit']) : 10;
     
    170175    public function get_referrers(\WP_REST_Request $request): \WP_REST_Response
    171176    {
     177        $timezone = wp_timezone();
    172178        $params             = $request->get_query_params();
    173         $start_date         = $params['start_date'] ?? create_local_datetime('first day of this month')->format('Y-m-d');
    174         $end_date           = $params['end_date'] ?? create_local_datetime('now')->format('Y-m-d');
     179        $start_date         = $params['start_date'] ?? (new DateTimeImmutable('first day of this month', $timezone))->format('Y-m-d');
     180        $end_date           = $params['end_date'] ?? (new DateTimeImmutable('now', $timezone))->format('Y-m-d');
    175181        $offset             = isset($params['offset']) ? absint($params['offset']) : 0;
    176182        $limit              = isset($params['limit']) ? absint($params['limit']) : 10;
  • koko-analytics/trunk/src/Script_Loader.php

    r3350963 r3378632  
    1010
    1111use KokoAnalytics\Normalizers\Normalizer;
    12 use WP_User;
    1312
    1413class Script_Loader
    1514{
    16     /**
    17      * @param bool $echo Whether to use the default WP script enqueue method or print the script tag directly
    18      */
    19     public static function maybe_enqueue_script(bool $echo = false): void
     15    public static function maybe_print_script(): void
    2016    {
    2117        $load_script = apply_filters('koko_analytics_load_tracking_script', true);
     
    2420        }
    2521
    26         if (is_request_excluded()) {
     22        if (is_request_excluded() || is_preview()) {
    2723            return;
    2824        }
    2925
    30         // TODO: Handle "term" requests so we track both terms and post types.
    31         add_filter('script_loader_tag', [ Script_Loader::class , 'add_async_attribute' ], 20, 2);
    32 
    33         if (false === $echo) {
    34             // Print configuration object early on in the HTML so scripts can modify it
    35             if (did_action('wp_head')) {
    36                 self::print_js_object();
    37             } else {
    38                 add_action('wp_head', [ Script_Loader::class , 'print_js_object' ], 1, 0);
    39             }
    40 
    41             // Enqueue the actual tracking script (in footer, if possible)
    42             wp_enqueue_script('koko-analytics', plugins_url('assets/dist/js/script.js', KOKO_ANALYTICS_PLUGIN_FILE), [], KOKO_ANALYTICS_VERSION, true);
    43         } else {
    44             self::print_js_object();
    45             echo '<script defer src="', plugins_url('assets/dist/js/script.js?ver=' . KOKO_ANALYTICS_VERSION, KOKO_ANALYTICS_PLUGIN_FILE), '"></script>';
    46         }
     26        echo PHP_EOL . '<!-- Koko Analytics v' . KOKO_ANALYTICS_VERSION . ' - https://www.kokoanalytics.com/ -->' . PHP_EOL;
     27        wp_print_inline_script_tag(file_get_contents(KOKO_ANALYTICS_PLUGIN_DIR . '/assets/dist/js/script.js'));
     28        echo PHP_EOL;
    4729    }
    4830
     
    6345    private static function get_tracker_url(): string
    6446    {
    65         global $wp;
    66 
    6747        // People can create their own endpoint and define it through this constant
    6848        if (\defined('KOKO_ANALYTICS_CUSTOM_ENDPOINT') && KOKO_ANALYTICS_CUSTOM_ENDPOINT) {
     
    8060    public static function get_request_path(): string
    8161    {
    82         $path = trim($_SERVER["REQUEST_URI"] ?? '');
    83         return Normalizer::path($path);
     62        return Normalizer::path(trim($_SERVER["REQUEST_URI"] ?? ''));
    8463    }
    8564
     
    132111        echo '<amp-analytics><script type="application/json">', json_encode($config), '</script></amp-analytics>';
    133112    }
    134 
    135     /**
    136      * @param string $tag
    137      * @param string $handle
    138      */
    139     public static function add_async_attribute($tag, $handle)
    140     {
    141         if ($handle !== 'koko-analytics' || strpos($tag, ' defer') !== false) {
    142             return $tag;
    143         }
    144 
    145         return str_replace(' src=', ' defer src=', $tag);
    146     }
    147113}
  • koko-analytics/trunk/src/Shortcodes/Shortcode_Site_Counter.php

    r3364374 r3378632  
    2020use KokoAnalytics\Normalizers\Normalizer;
    2121use KokoAnalytics\Stats;
    22 
    23 use function KokoAnalytics\create_local_datetime;
    2422
    2523class Shortcode_Site_Counter
  • koko-analytics/trunk/src/Stats.php

    r3366958 r3378632  
    2828    public function get_totals(string $start_date, string $end_date, $page = 0, $unused = null): object
    2929    {
    30         /** @var wpdb $wpdb */
     30        /** @var \wpdb $wpdb */
    3131        global $wpdb;
    3232
     
    7878    public function get_stats(string $start_date, string $end_date, string $group = 'day', $page = ''): array
    7979    {
    80         /** @var wpdb $wpdb */
     80        /** @var \wpdb $wpdb */
    8181        global $wpdb;
    8282
     
    121121    public function get_posts(string $start_date, string $end_date, int $offset = 0, int $limit = 10): array
    122122    {
    123         /** @var wpdb $wpdb */
     123        /** @var \wpdb $wpdb */
    124124        global $wpdb;
    125125
     
    128128                FROM {$wpdb->prefix}koko_analytics_post_stats s
    129129                JOIN {$wpdb->prefix}koko_analytics_paths p ON p.id = s.path_id
    130                 LEFT JOIN {$wpdb->prefix}posts wp ON s.post_id = wp.ID
     130                LEFT JOIN {$wpdb->prefix}posts wp ON wp.ID = s.post_id
    131131                WHERE s.date >= %s AND s.date <= %s
    132132                GROUP BY p.path, s.post_id
     
    138138        return array_map(function ($row) {
    139139            $row->pageviews = (int) $row->pageviews;
    140             $row->visitors = (int) $row->visitors;
     140            $row->visitors = max(1, (int) $row->visitors);
    141141
    142142            // for backwards compatibility with versions before 2.0
     
    151151    public function count_posts(string $start_date, string $end_date): int
    152152    {
    153         /** @var wpdb $wpdb */
     153        /** @var \wpdb $wpdb */
    154154        global $wpdb;
    155155        return (int) $wpdb->get_var($wpdb->prepare(
     
    169169    public function get_referrers(string $start_date, string $end_date, int $offset = 0, int $limit = 10): array
    170170    {
    171         /** @var wpdb $wpdb */
    172         global $wpdb;
    173         return $wpdb->get_results($wpdb->prepare(
     171        /** @var \wpdb $wpdb */
     172        global $wpdb;
     173
     174        return array_map(function ($row) {
     175            $row->pageviews = (int) $row->pageviews;
     176            $row->visitors = max(1, (int) $row->visitors);
     177            return $row;
     178        }, $wpdb->get_results($wpdb->prepare(
    174179            "SELECT s.id, url, SUM(visitors) As visitors, SUM(pageviews) AS pageviews
    175180                FROM {$wpdb->prefix}koko_analytics_referrer_stats s
     
    180185                LIMIT %d, %d",
    181186            [$start_date, $end_date, $offset, $limit]
    182         ));
     187        )));
    183188    }
    184189
    185190    public function count_referrers(string $start_date, string $end_date): int
    186191    {
    187         /** @var wpdb $wpdb */
     192        /** @var \wpdb $wpdb */
    188193        global $wpdb;
    189194        return (int) $wpdb->get_var($wpdb->prepare(
Note: See TracChangeset for help on using the changeset viewer.