2026-04-15 19:53:03 +00:00
< ? php
/**
* Plugin Name : Ansico Stat Plugin
* Plugin URI : https :// ansico . dk / Ansico / Ansico - Stat - plugin
* Description : Simple WP plugin with basic website statistics .
2026-04-17 21:56:44 +00:00
* Version : 1.1 . 0.1
2026-04-15 19:53:03 +00:00
* Author : Andreas Andersen ( Ansico )
* Author URI : https :// ansico . dk
* Text Domain : ansico - stat - plugin
* Requires at least : 6.0
* Requires PHP : 7.4
* License : GPLv2 or later
* License URI : https :// www . gnu . org / licenses / gpl - 2.0 . html
*/
if ( ! defined ( 'ABSPATH' )) {
exit ;
}
if ( ! class_exists ( 'Ansico_Stat_Plugin' )) {
class Ansico_Stat_Plugin {
2026-04-17 21:56:44 +00:00
const VERSION = '1.1.0.1' ;
2026-04-15 19:53:03 +00:00
const OPTION_KEY = 'ansico_stat_plugin_settings' ;
const INSTALL_OPTION_KEY = 'ansico_stat_plugin_install_date' ;
const TOTAL_META_KEY = 'post_views_count' ;
const COOKIE_PREFIX = 'ansico_stat_seen_' ;
const COOKIE_TTL = 1800 ; // 30 minutes
const SCHEMA_VERSION_OPTION_KEY = 'ansico_stat_plugin_schema_version' ;
const MENU_SLUG_SETTINGS = 'ansico-stat-plugin-settings' ;
const MENU_SLUG_STATS = 'ansico-stat-plugin' ;
const MENU_SLUG_YEARLY = 'ansico-stat-plugin-yearly' ;
2026-04-17 21:56:44 +00:00
const MENU_SLUG_SINGLE = 'ansico-stat-plugin-single' ;
2026-04-15 19:53:03 +00:00
const COOKIE_TTL_MESSAGE = 1800 ;
public function __construct () {
register_activation_hook ( __FILE__ , [ __CLASS__ , 'activate' ]);
register_deactivation_hook ( __FILE__ , [ __CLASS__ , 'deactivate' ]);
add_action ( 'init' , [ $this , 'load_textdomain' ]);
add_action ( 'init' , [ $this , 'maybe_upgrade_schema' ], 5 );
add_action ( 'template_redirect' , [ $this , 'maybe_track_view' ]);
add_filter ( 'the_content' , [ $this , 'append_admin_view_count_to_content' ], 999 );
add_action ( 'wp_footer' , [ $this , 'render_admin_view_count_fallback' ], 99 );
add_action ( 'admin_menu' , [ $this , 'register_admin_page' ]);
2026-04-17 21:56:44 +00:00
add_action ( 'admin_bar_menu' , [ $this , 'register_frontend_admin_bar_link' ], 90 );
2026-04-15 19:53:03 +00:00
add_action ( 'admin_enqueue_scripts' , [ $this , 'enqueue_admin_assets' ]);
add_action ( 'wp_dashboard_setup' , [ $this , 'register_dashboard_widget' ]);
add_action ( 'admin_post_ansico_stat_export_csv' , [ $this , 'handle_export_csv' ]);
2026-04-17 21:56:44 +00:00
add_action ( 'admin_post_ansico_stat_export_single_csv' , [ $this , 'handle_export_single_csv' ]);
2026-04-15 19:53:03 +00:00
add_action ( 'admin_post_ansico_stat_reset_all' , [ $this , 'handle_reset_all_views' ]);
add_action ( 'admin_post_ansico_stat_save_settings' , [ $this , 'handle_save_settings' ]);
add_action ( 'admin_init' , [ $this , 'register_admin_columns' ]);
add_action ( 'restrict_manage_posts' , [ $this , 'render_admin_view_filter' ]);
add_action ( 'pre_get_posts' , [ $this , 'handle_admin_list_sorting' ]);
add_filter ( 'posts_clauses' , [ $this , 'add_monthly_views_sorting_clauses' ], 10 , 2 );
}
public static function activate () {
global $wpdb ;
require_once ABSPATH . 'wp-admin/includes/upgrade.php' ;
$charset_collate = $wpdb -> get_charset_collate ();
$daily_table = self :: daily_table_name ();
$post_daily_table = self :: post_daily_table_name ();
$page_daily_table = self :: page_daily_table_name ();
$referral_table = self :: referral_table_name ();
$dimension_table = self :: dimension_table_name ();
$sql_daily = " CREATE TABLE { $daily_table } (
stat_date date NOT NULL ,
views bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
PRIMARY KEY ( stat_date )
) { $charset_collate }; " ;
$sql_post_daily = " CREATE TABLE { $post_daily_table } (
post_id bigint ( 20 ) unsigned NOT NULL ,
stat_date date NOT NULL ,
views bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
2026-04-17 21:56:44 +00:00
unique_visitors bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
2026-04-15 19:53:03 +00:00
PRIMARY KEY ( post_id , stat_date ),
KEY stat_date ( stat_date ),
KEY views ( views )
) { $charset_collate }; " ;
$sql_page_daily = " CREATE TABLE { $page_daily_table } (
page_key varchar ( 191 ) NOT NULL ,
page_type varchar ( 32 ) NOT NULL ,
object_id bigint ( 20 ) unsigned NULL DEFAULT NULL ,
page_label varchar ( 255 ) NOT NULL ,
page_url text NULL ,
stat_date date NOT NULL ,
views bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
2026-04-17 21:56:44 +00:00
unique_visitors bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
2026-04-15 19:53:03 +00:00
PRIMARY KEY ( page_key , stat_date ),
KEY stat_date ( stat_date ),
KEY page_type ( page_type ),
KEY object_id ( object_id ),
KEY views ( views )
) { $charset_collate }; " ;
$sql_referral = " CREATE TABLE { $referral_table } (
id bigint ( 20 ) unsigned NOT NULL AUTO_INCREMENT ,
stat_date date NOT NULL ,
category varchar ( 32 ) NOT NULL ,
source_url text NULL ,
source_host varchar ( 191 ) NULL ,
visits bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
PRIMARY KEY ( id ),
UNIQUE KEY unique_referral ( stat_date , category , source_host ( 191 ), source_url ( 191 )),
KEY stat_date ( stat_date ),
KEY category ( category ),
KEY source_host ( source_host )
) { $charset_collate }; " ;
$sql_dimensions = " CREATE TABLE { $dimension_table } (
stat_date date NOT NULL ,
dimension_type varchar ( 32 ) NOT NULL ,
dimension_value varchar ( 191 ) NOT NULL ,
views bigint ( 20 ) unsigned NOT NULL DEFAULT 0 ,
PRIMARY KEY ( stat_date , dimension_type , dimension_value ),
KEY dimension_type ( dimension_type ),
KEY views ( views )
) { $charset_collate }; " ;
dbDelta ( $sql_daily );
dbDelta ( $sql_post_daily );
dbDelta ( $sql_page_daily );
dbDelta ( $sql_referral );
dbDelta ( $sql_dimensions );
if ( ! get_option ( self :: INSTALL_OPTION_KEY )) {
update_option ( self :: INSTALL_OPTION_KEY , current_time ( 'Y-m-d' ), false );
}
update_option ( self :: SCHEMA_VERSION_OPTION_KEY , self :: VERSION , false );
}
public static function deactivate () {
// Keep data on deactivation.
}
public function maybe_upgrade_schema () {
$stored_version = ( string ) get_option ( self :: SCHEMA_VERSION_OPTION_KEY , '' );
if ( $stored_version === self :: VERSION ) {
return ;
}
self :: activate ();
}
public function load_textdomain () {
load_plugin_textdomain ( 'ansico-stat-plugin' , false , dirname ( plugin_basename ( __FILE__ )) . '/languages' );
}
public static function daily_table_name () {
global $wpdb ;
return $wpdb -> prefix . 'ansico_stat_daily' ;
}
public static function post_daily_table_name () {
global $wpdb ;
return $wpdb -> prefix . 'ansico_stat_post_daily' ;
}
public static function page_daily_table_name () {
global $wpdb ;
return $wpdb -> prefix . 'ansico_stat_page_daily' ;
}
public static function referral_table_name () {
global $wpdb ;
return $wpdb -> prefix . 'ansico_stat_referrals' ;
}
public static function dimension_table_name () {
global $wpdb ;
return $wpdb -> prefix . 'ansico_stat_dimensions' ;
}
protected function get_default_track_post_types () {
$post_types = get_post_types ([ 'public' => true ], 'names' );
$post_types = array_filter ( $post_types , function ( $post_type ) {
return ! in_array ( $post_type , [ 'attachment' , 'revision' , 'nav_menu_item' , 'custom_css' , 'customize_changeset' , 'oembed_cache' , 'user_request' , 'wp_block' , 'wp_template' , 'wp_template_part' , 'wp_navigation' , 'wp_global_styles' ], true );
});
return array_values ( $post_types );
}
protected function get_settings () {
$settings = get_option ( self :: OPTION_KEY , []);
if ( ! is_array ( $settings )) {
$settings = [];
}
$settings = wp_parse_args ( $settings , [
'visitor_counting_mode' => 'count_all' ,
'exclude_known_bots' => 1 ,
'top_list_rows' => 10 ,
'referral_rows' => 25 ,
'revisit_minutes' => 30 ,
'frontend_label' => 'Views' ,
'track_post_types' => $this -> get_default_track_post_types (),
'track_front_page' => 1 ,
'track_posts_page' => 1 ,
'track_archives' => 1 ,
'track_search' => 1 ,
'track_404' => 1 ,
]);
if ( ! is_array ( $settings [ 'track_post_types' ])) {
$settings [ 'track_post_types' ] = $this -> get_default_track_post_types ();
}
$settings [ 'track_post_types' ] = array_values ( array_filter ( array_map ( 'sanitize_key' , $settings [ 'track_post_types' ])));
$settings [ 'visitor_counting_mode' ] = in_array ( $settings [ 'visitor_counting_mode' ], [ 'count_all' , 'exclude_admins' , 'exclude_all_logged_in' ], true ) ? $settings [ 'visitor_counting_mode' ] : 'count_all' ;
$settings [ 'revisit_minutes' ] = max ( 1 , min ( 10080 , ( int ) $settings [ 'revisit_minutes' ]));
$settings [ 'frontend_label' ] = sanitize_text_field (( string ) $settings [ 'frontend_label' ]);
if ( $settings [ 'frontend_label' ] === '' ) {
$settings [ 'frontend_label' ] = 'Views' ;
}
return $settings ;
}
protected function get_cookie_ttl () {
$settings = $this -> get_settings ();
return max ( 60 , ( int ) $settings [ 'revisit_minutes' ] * 60 );
}
protected function should_exclude_current_user () {
$settings = $this -> get_settings ();
$mode = $settings [ 'visitor_counting_mode' ] ? ? 'count_all' ;
if ( $mode === 'exclude_all_logged_in' && is_user_logged_in ()) {
return true ;
}
if ( $mode === 'exclude_admins' && current_user_can ( 'manage_options' )) {
return true ;
}
return false ;
}
protected function is_tracking_enabled_for_context ( array $page_context ) {
$settings = $this -> get_settings ();
$page_type = ( string ) ( $page_context [ 'page_type' ] ? ? '' );
if (( $page_context [ 'kind' ] ? ? '' ) === 'singular' ) {
$post_type = sanitize_key (( string ) ( $page_context [ 'post_type' ] ? ? get_post_type (( int ) ( $page_context [ 'post_id' ] ? ? 0 ))));
return in_array ( $post_type , $settings [ 'track_post_types' ], true );
}
if ( $page_type === 'front_page' ) {
return ! empty ( $settings [ 'track_front_page' ]);
}
if ( $page_type === 'home' ) {
return ! empty ( $settings [ 'track_posts_page' ]);
}
if ( $page_type === 'search' ) {
return ! empty ( $settings [ 'track_search' ]);
}
if ( $page_type === '404' ) {
return ! empty ( $settings [ 'track_404' ]);
}
return ! empty ( $settings [ 'track_archives' ]);
}
protected function get_frontend_icon_markup () {
return '<span class="ansico-stat-frontend-icon" aria-hidden="true" style="display:inline-flex;vertical-align:middle;margin-right:.45rem;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="3" y="11" width="3" height="9" rx="1" fill="currentColor"/><rect x="10.5" y="7" width="3" height="13" rx="1" fill="currentColor"/><rect x="18" y="3" width="3" height="17" rx="1" fill="currentColor"/></svg></span>' ;
}
protected function get_country_code () {
$candidates = [
$_SERVER [ 'HTTP_CF_IPCOUNTRY' ] ? ? '' ,
$_SERVER [ 'GEOIP_COUNTRY_CODE' ] ? ? '' ,
$_SERVER [ 'HTTP_X_COUNTRY_CODE' ] ? ? '' ,
$_SERVER [ 'HTTP_X_APPENGINE_COUNTRY' ] ? ? '' ,
];
foreach ( $candidates as $candidate ) {
$code = strtoupper ( sanitize_text_field (( string ) $candidate ));
if ( preg_match ( '/^[A-Z]{2}$/' , $code )) {
return $code ;
}
}
return 'Unknown' ;
}
protected function get_country_label ( string $country_code ) {
$country_code = strtoupper ( trim ( $country_code ));
if ( $country_code === '' || $country_code === 'UNKNOWN' ) {
return __ ( 'Unknown' , 'ansico-stat-plugin' );
}
if ( function_exists ( 'locale_get_display_region' )) {
$label = locale_get_display_region ( '-' . $country_code , get_locale ());
if ( is_string ( $label ) && $label !== '' ) {
return $label ;
}
}
return $country_code ;
}
protected function get_device_type () {
$user_agent = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) ? strtolower (( string ) wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ])) : '' ;
if ( $user_agent === '' ) {
return 'Unknown' ;
}
if ( strpos ( $user_agent , 'tablet' ) !== false || strpos ( $user_agent , 'ipad' ) !== false || strpos ( $user_agent , 'kindle' ) !== false || strpos ( $user_agent , 'playbook' ) !== false ) {
return 'Tablet' ;
}
if ( strpos ( $user_agent , 'mobile' ) !== false || strpos ( $user_agent , 'iphone' ) !== false || strpos ( $user_agent , 'android' ) !== false || strpos ( $user_agent , 'phone' ) !== false ) {
return 'Mobile' ;
}
return 'Desktop' ;
}
protected function get_browser_name () {
$user_agent = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) ? strtolower (( string ) wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ])) : '' ;
$browsers = [
'edg/' => 'Edge' ,
'opr/' => 'Opera' ,
'opera' => 'Opera' ,
'brave' => 'Brave' ,
'firefox' => 'Firefox' ,
'samsungbrowser' => 'Samsung Internet' ,
'chrome' => 'Chrome' ,
'crios' => 'Chrome' ,
'safari' => 'Safari' ,
'trident/' => 'Internet Explorer' ,
'msie' => 'Internet Explorer' ,
];
foreach ( $browsers as $needle => $label ) {
if ( strpos ( $user_agent , $needle ) !== false ) {
if ( $label === 'Safari' && ( strpos ( $user_agent , 'chrome' ) !== false || strpos ( $user_agent , 'crios' ) !== false || strpos ( $user_agent , 'android' ) !== false )) {
continue ;
}
return $label ;
}
}
return 'Other' ;
}
protected function get_os_name () {
$user_agent = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) ? strtolower (( string ) wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ])) : '' ;
$systems = [
'windows nt' => 'Windows' ,
'iphone' => 'iOS' ,
'ipad' => 'iPadOS' ,
'mac os x' => 'macOS' ,
'android' => 'Android' ,
'linux' => 'Linux' ,
'cros' => 'Chrome OS' ,
];
foreach ( $systems as $needle => $label ) {
if ( strpos ( $user_agent , $needle ) !== false ) {
return $label ;
}
}
return 'Other' ;
}
protected function increment_dimension_views ( string $today ) {
global $wpdb ;
$table = self :: dimension_table_name ();
$dimensions = [
'country' => $this -> get_country_code (),
'device' => $this -> get_device_type (),
'browser' => $this -> get_browser_name (),
'os' => $this -> get_os_name (),
];
foreach ( $dimensions as $dimension_type => $dimension_value ) {
$dimension_value = substr ( sanitize_text_field (( string ) $dimension_value ), 0 , 191 );
if ( $dimension_value === '' ) {
$dimension_value = 'Unknown' ;
}
$wpdb -> query ( $wpdb -> prepare (
" INSERT INTO { $table } (stat_date, dimension_type, dimension_value, views)
VALUES ( % s , % s , % s , 1 )
ON DUPLICATE KEY UPDATE views = views + 1 " ,
$today ,
$dimension_type ,
$dimension_value
));
}
}
protected function is_known_bot_request () {
$user_agent = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) ? strtolower (( string ) wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ])) : '' ;
if ( $user_agent === '' ) {
return true ;
}
$bot_markers = [
'bot' , 'crawl' , 'crawler' , 'spider' , 'slurp' , 'facebookexternalhit' , 'bingpreview' ,
'headless' , 'preview' , 'python-requests' , 'curl' , 'wget' , 'uptimerobot' , 'monitoring' ,
'phantomjs' , 'feedfetcher' , 'google-read-aloud' , 'scanner' , 'validator' , 'scrapy' ,
'axios' , 'go-http-client' , 'node-fetch' , 'httpclient' , 'libwww-perl'
];
foreach ( $bot_markers as $marker ) {
if ( strpos ( $user_agent , $marker ) !== false ) {
return true ;
}
}
if ( ! empty ( $_SERVER [ 'HTTP_X_PURPOSE' ]) || ! empty ( $_SERVER [ 'HTTP_X_MOZ' ]) || ( ! empty ( $_SERVER [ 'HTTP_SEC_FETCH_SITE' ]) && strtolower (( string ) $_SERVER [ 'HTTP_SEC_FETCH_SITE' ]) === 'none' && strpos ( $user_agent , 'mozilla' ) === false )) {
return true ;
}
return false ;
}
protected function is_trackable_post ( $post ) {
if ( ! $post || empty ( $post -> ID ) || ! is_object ( $post )) {
return false ;
}
$post_type_obj = get_post_type_object ( $post -> post_type );
if ( ! $post_type_obj || empty ( $post_type_obj -> public )) {
return false ;
}
if ( post_password_required ( $post )) {
return false ;
}
return true ;
}
protected function get_referer_data () {
$referer = isset ( $_SERVER [ 'HTTP_REFERER' ]) ? trim (( string ) wp_unslash ( $_SERVER [ 'HTTP_REFERER' ])) : '' ;
$site_host = wp_parse_url ( home_url (), PHP_URL_HOST );
$site_host = $site_host ? strtolower (( string ) $site_host ) : '' ;
if ( $referer === '' ) {
return [
'category' => 'direct' ,
'source_url' => '' ,
'source_host' => '' ,
];
}
$host = wp_parse_url ( $referer , PHP_URL_HOST );
$host = $host ? strtolower (( string ) $host ) : '' ;
if ( $host === '' || $host === $site_host || preg_replace ( '/^www\./' , '' , $host ) === preg_replace ( '/^www\./' , '' , $site_host )) {
return [
'category' => 'direct' ,
'source_url' => '' ,
'source_host' => '' ,
];
}
$search_hosts = [
'google.' , 'bing.com' , 'search.yahoo.' , 'duckduckgo.com' , 'yandex.' , 'baidu.com' , 'ecosia.org' ,
'startpage.com' , 'qwant.com' , 'search.brave.com'
];
foreach ( $search_hosts as $needle ) {
if ( strpos ( $host , $needle ) !== false ) {
return [
'category' => 'search' ,
'source_url' => esc_url_raw ( $referer ),
'source_host' => sanitize_text_field ( $host ),
];
}
}
$social_hosts = [
'facebook.com' , 'm.facebook.com' , 'l.facebook.com' , 'lm.facebook.com' , 'instagram.com' ,
't.co' , 'twitter.com' , 'x.com' , 'linkedin.com' , 'lnkd.in' , 'pinterest.' , 'reddit.com' ,
'youtube.com' , 'youtu.be' , 'tiktok.com' , 'snapchat.com' , 'threads.net' , 'messenger.com'
];
foreach ( $social_hosts as $needle ) {
if ( strpos ( $host , $needle ) !== false ) {
return [
'category' => 'social' ,
'source_url' => esc_url_raw ( $referer ),
'source_host' => sanitize_text_field ( $host ),
];
}
}
return [
'category' => 'website' ,
'source_url' => esc_url_raw ( $referer ),
'source_host' => sanitize_text_field ( $host ),
];
}
public function maybe_track_view () {
if ( is_admin () || wp_doing_ajax () || wp_doing_cron () || is_feed () || is_preview () || is_trackback () || is_robots ()) {
return ;
}
if ( ! $this -> is_trackable_request ()) {
return ;
}
$settings = $this -> get_settings ();
if ( $this -> should_exclude_current_user ()) {
return ;
}
if ( ! empty ( $settings [ 'exclude_known_bots' ]) && $this -> is_known_bot_request ()) {
return ;
}
$page_context = $this -> get_current_page_context ();
if ( ! $page_context || ! $this -> is_tracking_enabled_for_context ( $page_context )) {
return ;
}
2026-04-17 21:56:44 +00:00
$view_cookie_name = self :: COOKIE_PREFIX . md5 ( $page_context [ 'cookie_key' ]);
$unique_cookie_name = self :: COOKIE_PREFIX . 'unique_' . md5 ( $page_context [ 'cookie_key' ] . '|' . current_time ( 'Y-m-d' ));
$is_unique_visitor = ! isset ( $_COOKIE [ $unique_cookie_name ]);
if ( isset ( $_COOKIE [ $view_cookie_name ])) {
2026-04-15 19:53:03 +00:00
return ;
}
$referer_data = $this -> get_referer_data ();
if ( $page_context [ 'kind' ] === 'singular' ) {
2026-04-17 21:56:44 +00:00
$this -> increment_post_views (( int ) $page_context [ 'post_id' ], $referer_data , $is_unique_visitor );
2026-04-15 19:53:03 +00:00
} else {
2026-04-17 21:56:44 +00:00
$this -> increment_non_singular_views ( $page_context , $referer_data , $is_unique_visitor );
2026-04-15 19:53:03 +00:00
}
if ( ! headers_sent ()) {
2026-04-17 21:56:44 +00:00
setcookie ( $view_cookie_name , '1' , time () + $this -> get_cookie_ttl (), COOKIEPATH ? : '/' , COOKIE_DOMAIN , is_ssl (), true );
$_COOKIE [ $view_cookie_name ] = '1' ;
if ( $is_unique_visitor ) {
$expires = strtotime ( 'tomorrow' , current_time ( 'timestamp' ));
if ( $expires <= time ()) {
$expires = time () + DAY_IN_SECONDS ;
}
setcookie ( $unique_cookie_name , '1' , $expires , COOKIEPATH ? : '/' , COOKIE_DOMAIN , is_ssl (), true );
$_COOKIE [ $unique_cookie_name ] = '1' ;
}
2026-04-15 19:53:03 +00:00
}
}
protected function is_trackable_request () {
return is_singular () || is_404 () || is_home () || is_front_page () || is_post_type_archive () || is_category () || is_tag () || is_tax () || is_author () || is_date () || is_search () || is_archive ();
}
protected function normalize_request_uri () {
$uri = isset ( $_SERVER [ 'REQUEST_URI' ]) ? ( string ) wp_unslash ( $_SERVER [ 'REQUEST_URI' ]) : '/' ;
$path = wp_parse_url ( $uri , PHP_URL_PATH );
$path = is_string ( $path ) && $path !== '' ? $path : '/' ;
return untrailingslashit ( $path ) ? : '/' ;
}
protected function get_current_page_context () {
if ( is_admin ()) {
return null ;
}
if ( is_singular ()) {
$post = $this -> get_current_singular_post ();
if ( ! $post ) {
return null ;
}
return [
'kind' => 'singular' ,
'post_id' => ( int ) $post -> ID ,
'post_type' => sanitize_key (( string ) $post -> post_type ),
'cookie_key' => 'post:' . ( int ) $post -> ID ,
'page_key' => 'post:' . ( int ) $post -> ID ,
'page_type' => sanitize_key (( string ) $post -> post_type ),
'object_id' => ( int ) $post -> ID ,
'label' => get_the_title ( $post -> ID ) ? : __ ( '(no title)' , 'ansico-stat-plugin' ),
'url' => get_permalink ( $post -> ID ) ? : '' ,
];
}
$request_path = $this -> normalize_request_uri ();
$base = [
'kind' => 'archive' ,
'cookie_key' => $request_path ,
'page_key' => '' ,
'page_type' => 'archive' ,
'object_id' => 0 ,
'label' => '' ,
'url' => home_url ( $request_path . '/' ),
];
if ( is_404 ()) {
$base [ 'page_type' ] = '404' ;
$base [ 'page_key' ] = '404:' . md5 ( $request_path );
$base [ 'label' ] = sprintf ( __ ( '404: %s' , 'ansico-stat-plugin' ), $request_path );
$base [ 'url' ] = home_url ( $request_path === '/' ? '/' : $request_path . '/' );
return $base ;
}
if ( is_category () || is_tag () || is_tax ()) {
$term = get_queried_object ();
if ( ! $term || empty ( $term -> term_id )) {
return null ;
}
$taxonomy = is_string ( $term -> taxonomy ? ? '' ) ? $term -> taxonomy : 'taxonomy' ;
$base [ 'page_type' ] = $taxonomy ;
$base [ 'page_key' ] = 'term:' . $taxonomy . ':' . ( int ) $term -> term_id ;
$base [ 'cookie_key' ] = $base [ 'page_key' ];
$base [ 'object_id' ] = ( int ) $term -> term_id ;
$base [ 'label' ] = single_term_title ( '' , false ) ? : ( $term -> name ? ? $request_path );
$base [ 'url' ] = get_term_link ( $term );
return $base ;
}
if ( is_author ()) {
$author = get_queried_object ();
if ( ! $author || empty ( $author -> ID )) {
return null ;
}
$base [ 'page_type' ] = 'author' ;
$base [ 'page_key' ] = 'author:' . ( int ) $author -> ID ;
$base [ 'cookie_key' ] = $base [ 'page_key' ];
$base [ 'object_id' ] = ( int ) $author -> ID ;
$base [ 'label' ] = get_the_author_meta ( 'display_name' , ( int ) $author -> ID ) ? : $request_path ;
$base [ 'url' ] = get_author_posts_url (( int ) $author -> ID );
return $base ;
}
if ( is_post_type_archive ()) {
$post_type = get_query_var ( 'post_type' );
$post_type = is_array ( $post_type ) ? reset ( $post_type ) : $post_type ;
$post_type = sanitize_key (( string ) $post_type );
if ( $post_type === '' ) {
$post_type = 'archive' ;
}
$base [ 'page_type' ] = 'post_type_archive' ;
$base [ 'page_key' ] = 'post_type_archive:' . $post_type ;
$base [ 'cookie_key' ] = $base [ 'page_key' ];
$base [ 'label' ] = post_type_archive_title ( '' , false ) ? : $request_path ;
$archive_link = get_post_type_archive_link ( $post_type );
$base [ 'url' ] = $archive_link ? : $base [ 'url' ];
return $base ;
}
if ( is_home () || is_front_page ()) {
$base [ 'page_type' ] = is_front_page () ? 'front_page' : 'home' ;
$base [ 'page_key' ] = $base [ 'page_type' ];
$base [ 'cookie_key' ] = $base [ 'page_key' ];
$base [ 'label' ] = is_front_page () ? __ ( 'Front page' , 'ansico-stat-plugin' ) : __ ( 'Posts page' , 'ansico-stat-plugin' );
$base [ 'url' ] = home_url ( '/' );
return $base ;
}
if ( is_date ()) {
$base [ 'page_type' ] = 'date' ;
$base [ 'page_key' ] = 'date:' . md5 ( $request_path );
$base [ 'label' ] = wp_get_document_title () ? : $request_path ;
return $base ;
}
if ( is_search ()) {
$search_query = get_search_query ();
$base [ 'page_type' ] = 'search' ;
$base [ 'page_key' ] = 'search:' . md5 (( string ) $search_query );
$base [ 'cookie_key' ] = 'search:' . md5 (( string ) $search_query );
$base [ 'label' ] = sprintf ( __ ( 'Search: %s' , 'ansico-stat-plugin' ), $search_query ? : __ ( '(empty)' , 'ansico-stat-plugin' ));
$base [ 'url' ] = get_search_link ( $search_query );
return $base ;
}
if ( is_archive ()) {
$base [ 'page_type' ] = 'archive' ;
$base [ 'page_key' ] = 'archive:' . md5 ( $request_path );
$base [ 'label' ] = wp_get_document_title () ? : $request_path ;
return $base ;
}
return null ;
}
2026-04-17 21:56:44 +00:00
protected function increment_post_views ( int $post_id , array $referer_data = [], bool $is_unique_visitor = false ) {
2026-04-15 19:53:03 +00:00
global $wpdb ;
$today = current_time ( 'Y-m-d' );
$daily_table = self :: daily_table_name ();
$post_daily_table = self :: post_daily_table_name ();
$page_daily_table = self :: page_daily_table_name ();
$referral_table = self :: referral_table_name ();
$current_total = ( int ) get_post_meta ( $post_id , self :: TOTAL_META_KEY , true );
update_post_meta ( $post_id , self :: TOTAL_META_KEY , $current_total + 1 );
$wpdb -> query ( $wpdb -> prepare (
" INSERT INTO { $daily_table } (stat_date, views)
VALUES ( % s , 1 )
ON DUPLICATE KEY UPDATE views = views + 1 " ,
$today
));
$wpdb -> query ( $wpdb -> prepare (
2026-04-17 21:56:44 +00:00
" INSERT INTO { $post_daily_table } (post_id, stat_date, views, unique_visitors)
VALUES ( % d , % s , 1 , % d )
ON DUPLICATE KEY UPDATE views = views + 1 , unique_visitors = unique_visitors + VALUES ( unique_visitors ) " ,
2026-04-15 19:53:03 +00:00
$post_id ,
2026-04-17 21:56:44 +00:00
$today ,
$is_unique_visitor ? 1 : 0
2026-04-15 19:53:03 +00:00
));
$category = isset ( $referer_data [ 'category' ]) ? sanitize_key ( $referer_data [ 'category' ]) : 'direct' ;
$source_url = isset ( $referer_data [ 'source_url' ]) ? esc_url_raw (( string ) $referer_data [ 'source_url' ]) : '' ;
$source_host = isset ( $referer_data [ 'source_host' ]) ? sanitize_text_field (( string ) $referer_data [ 'source_host' ]) : '' ;
$wpdb -> query ( $wpdb -> prepare (
" INSERT INTO { $referral_table } (stat_date, category, source_url, source_host, visits)
VALUES ( % s , % s , % s , % s , 1 )
ON DUPLICATE KEY UPDATE visits = visits + 1 " ,
$today ,
$category ,
$source_url ,
$source_host
));
$this -> increment_dimension_views ( $today );
}
2026-04-17 21:56:44 +00:00
protected function increment_non_singular_views ( array $page_context , array $referer_data = [], bool $is_unique_visitor = false ) {
2026-04-15 19:53:03 +00:00
global $wpdb ;
$today = current_time ( 'Y-m-d' );
$daily_table = self :: daily_table_name ();
$page_daily_table = self :: page_daily_table_name ();
$referral_table = self :: referral_table_name ();
$wpdb -> query ( $wpdb -> prepare (
" INSERT INTO { $daily_table } (stat_date, views)
VALUES ( % s , 1 )
ON DUPLICATE KEY UPDATE views = views + 1 " ,
$today
));
$page_key = substr ( sanitize_text_field (( string ) $page_context [ 'page_key' ]), 0 , 191 );
$page_type = substr ( sanitize_key (( string ) $page_context [ 'page_type' ]), 0 , 32 );
$object_id = ! empty ( $page_context [ 'object_id' ]) ? ( int ) $page_context [ 'object_id' ] : null ;
$label = sanitize_text_field (( string ) $page_context [ 'label' ]);
$url = isset ( $page_context [ 'url' ]) ? esc_url_raw (( string ) $page_context [ 'url' ]) : '' ;
$wpdb -> query ( $wpdb -> prepare (
2026-04-17 21:56:44 +00:00
" INSERT INTO { $page_daily_table } (page_key, page_type, object_id, page_label, page_url, stat_date, views, unique_visitors)
VALUES ( % s , % s , % s , % s , % s , % s , 1 , % d )
ON DUPLICATE KEY UPDATE views = views + 1 , unique_visitors = unique_visitors + VALUES ( unique_visitors ), page_label = VALUES ( page_label ), page_url = VALUES ( page_url ) " ,
2026-04-15 19:53:03 +00:00
$page_key ,
$page_type ,
$object_id ,
$label ,
$url ,
2026-04-17 21:56:44 +00:00
$today ,
$is_unique_visitor ? 1 : 0
2026-04-15 19:53:03 +00:00
));
$category = isset ( $referer_data [ 'category' ]) ? sanitize_key ( $referer_data [ 'category' ]) : 'direct' ;
$source_url = isset ( $referer_data [ 'source_url' ]) ? esc_url_raw (( string ) $referer_data [ 'source_url' ]) : '' ;
$source_host = isset ( $referer_data [ 'source_host' ]) ? sanitize_text_field (( string ) $referer_data [ 'source_host' ]) : '' ;
$wpdb -> query ( $wpdb -> prepare (
" INSERT INTO { $referral_table } (stat_date, category, source_url, source_host, visits)
VALUES ( % s , % s , % s , % s , 1 )
ON DUPLICATE KEY UPDATE visits = visits + 1 " ,
$today ,
$category ,
$source_url ,
$source_host
));
$this -> increment_dimension_views ( $today );
}
protected function get_non_singular_lifetime_views ( string $page_key ) {
global $wpdb ;
$table = self :: page_daily_table_name ();
$views = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(views) FROM { $table } WHERE page_key = %s " ,
$page_key
));
return ( int ) $views ;
}
protected function get_current_singular_post () {
if ( is_admin () || ! is_singular ()) {
return null ;
}
$post = get_queried_object ();
if ( ! $this -> is_trackable_post ( $post )) {
return null ;
}
return $post ;
}
protected function get_admin_view_markup ( array $page_context ) {
if ( ! $this -> is_tracking_enabled_for_context ( $page_context )) {
return '' ;
}
$views = 0 ;
if (( $page_context [ 'kind' ] ? ? '' ) === 'singular' && ! empty ( $page_context [ 'post_id' ])) {
$views = ( int ) get_post_meta (( int ) $page_context [ 'post_id' ], self :: TOTAL_META_KEY , true );
} elseif ( ! empty ( $page_context [ 'page_key' ])) {
$views = $this -> get_non_singular_lifetime_views (( string ) $page_context [ 'page_key' ]);
}
$settings = $this -> get_settings ();
$label = sanitize_text_field (( string ) ( $settings [ 'frontend_label' ] ? ? 'Views' ));
if ( $label === '' ) {
$label = 'Views' ;
}
$markup = '<div class="ansico-stat-frontend-views" data-ansico-stat-views="1" style="margin:2.75rem 0 1.75rem; padding:1rem 1rem 0; border-top:1px solid #ddd; font-size:14px; opacity:.95; display:flex; align-items:center; gap:.25rem; flex-wrap:wrap;">' ;
$markup .= $this -> get_frontend_icon_markup ();
$markup .= '<strong>' . esc_html ( $label . ':' ) . '</strong> ' . esc_html ( number_format_i18n ( $views ));
$markup .= '</div>' ;
return $markup ;
}
public function append_admin_view_count_to_content ( $content ) {
if ( is_admin () || ! is_singular () || ! in_the_loop () || ! is_main_query ()) {
return $content ;
}
if ( ! current_user_can ( 'manage_options' )) {
return $content ;
}
$page_context = $this -> get_current_page_context ();
if ( ! $page_context || ( $page_context [ 'kind' ] ? ? '' ) !== 'singular' ) {
return $content ;
}
$markup = $this -> get_admin_view_markup ( $page_context );
return $markup === '' ? $content : $content . $markup ;
}
public function render_admin_view_count_fallback () {
if ( is_admin () || ! current_user_can ( 'manage_options' ) || ! $this -> is_trackable_request ()) {
return ;
}
if ( is_singular ()) {
return ;
}
$page_context = $this -> get_current_page_context ();
if ( ! $page_context ) {
return ;
}
$markup_value = $this -> get_admin_view_markup ( $page_context );
if ( $markup_value === '' ) {
return ;
}
$markup = wp_json_encode ( $markup_value );
?>
< script >
( function () {
var markup = < ? php echo $markup ; ?> ;
if ( ! markup || document . querySelector ( '[data-ansico-stat-views="1"]' )) {
return ;
}
var selectors = [
'.entry-content' , '.post-content' , '.content' , 'main' , 'article' ,
'.site-main' , '.archive-description' , '.page-content' , '.taxonomy-description'
];
var target = null ;
for ( var i = 0 ; i < selectors . length ; i ++ ) {
target = document . querySelector ( selectors [ i ]);
if ( target ) {
break ;
}
}
if ( ! target ) {
target = document . querySelector ( '#secondary, .widget-area, aside, footer, .site-footer' );
}
if ( ! target ) {
target = document . body ;
}
var wrapper = document . createElement ( 'div' );
wrapper . innerHTML = markup ;
target . appendChild ( wrapper . firstElementChild );
})();
</ script >
< ? php
}
public function register_admin_page () {
add_menu_page (
__ ( 'Ansico Stat Plugin' , 'ansico-stat-plugin' ),
__ ( 'Ansico Stat Plugin' , 'ansico-stat-plugin' ),
'manage_options' ,
self :: MENU_SLUG_STATS ,
[ $this , 'render_monthly_stats_page' ],
'dashicons-chart-area' ,
58
);
add_submenu_page (
self :: MENU_SLUG_STATS ,
__ ( 'Monthly statistics' , 'ansico-stat-plugin' ),
__ ( 'Monthly statistics' , 'ansico-stat-plugin' ),
'manage_options' ,
self :: MENU_SLUG_STATS ,
[ $this , 'render_monthly_stats_page' ]
);
add_submenu_page (
self :: MENU_SLUG_STATS ,
__ ( 'Yearly statistics' , 'ansico-stat-plugin' ),
__ ( 'Yearly statistics' , 'ansico-stat-plugin' ),
'manage_options' ,
self :: MENU_SLUG_YEARLY ,
[ $this , 'render_yearly_stats_page' ]
);
2026-04-17 21:56:44 +00:00
add_submenu_page (
self :: MENU_SLUG_STATS ,
__ ( 'Single page statistics' , 'ansico-stat-plugin' ),
__ ( 'Single page statistics' , 'ansico-stat-plugin' ),
'manage_options' ,
self :: MENU_SLUG_SINGLE ,
[ $this , 'render_single_page_stats_page' ]
);
2026-04-15 19:53:03 +00:00
add_submenu_page (
self :: MENU_SLUG_STATS ,
__ ( 'Settings' , 'ansico-stat-plugin' ),
__ ( 'Settings' , 'ansico-stat-plugin' ),
'manage_options' ,
self :: MENU_SLUG_SETTINGS ,
[ $this , 'render_settings_page' ]
);
}
public function enqueue_admin_assets ( $hook ) {
if ( ! in_array ( $hook , [
'index.php' ,
'toplevel_page_' . self :: MENU_SLUG_STATS ,
'ansico-stat-plugin_page_' . self :: MENU_SLUG_YEARLY ,
2026-04-17 21:56:44 +00:00
'ansico-stat-plugin_page_' . self :: MENU_SLUG_SINGLE ,
2026-04-15 19:53:03 +00:00
'ansico-stat-plugin_page_' . self :: MENU_SLUG_SETTINGS ,
], true )) {
return ;
}
wp_enqueue_style (
'ansico-stat-admin' ,
plugin_dir_url ( __FILE__ ) . 'assets/css/ansico-stat-admin.css' ,
[],
self :: VERSION
);
}
public function register_dashboard_widget () {
wp_add_dashboard_widget (
'ansico_stat_dashboard_widget' ,
__ ( 'Ansico Stat Plugin: Daily Total Views' , 'ansico-stat-plugin' ),
[ $this , 'render_dashboard_widget' ]
);
}
protected function format_post_link ( int $post_id , string $label ) {
$permalink = get_permalink ( $post_id );
$label = $label ? : __ ( '(no title)' , 'ansico-stat-plugin' );
if ( ! $permalink ) {
return esc_html ( $label );
}
return sprintf (
'<a href="%1$s" title="%2$s" data-permalink="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>' ,
esc_url ( $permalink ),
esc_attr ( $permalink ),
esc_html ( $label )
);
}
protected function format_external_link ( string $url ) {
if ( $url === '' ) {
return '—' ;
}
return sprintf (
'<a href="%1$s" title="%2$s" data-permalink="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>' ,
esc_url ( $url ),
esc_attr ( $url ),
esc_html ( $url )
);
}
public function render_dashboard_widget () {
$chart_data = $this -> get_daily_chart_data ( 30 );
$monthly_total = array_sum ( wp_list_pluck ( $chart_data , 'views' ));
$top_posts = $this -> get_top_posts_in_period ( $this -> date_days_ago ( 29 ), current_time ( 'Y-m-d' ), 10 );
echo '<div class="ansico-stat-widget">' ;
echo '<p class="ansico-stat-widget-total">' . esc_html ( sprintf ( __ ( 'Total tracked views in the last 30 days: %s' , 'ansico-stat-plugin' ), number_format_i18n ( $monthly_total ))) . '</p>' ;
echo '<div class="ansico-stat-widget-chart-wrap">' ;
echo $this -> render_chart_markup ( $chart_data , 'ansico-stat-widget-chart' , [ 'is_widget' => true ]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>' ;
echo '<h4>' . esc_html__ ( 'Top 10 views in the last 30 days' , 'ansico-stat-plugin' ) . '</h4>' ;
if ( empty ( $top_posts )) {
echo '<p>' . esc_html__ ( 'No tracked views yet.' , 'ansico-stat-plugin' ) . '</p>' ;
} else {
echo '<ol class="ansico-stat-top-list">' ;
foreach ( $top_posts as $row ) {
$post_id = ( int ) $row [ 'post_id' ];
$title = get_the_title ( $post_id ) ? : __ ( '(no title)' , 'ansico-stat-plugin' );
echo '<li>' ;
echo $this -> format_post_link ( $post_id , $title ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ' <span>(' . esc_html ( number_format_i18n (( int ) $row [ 'period_views' ])) . ')</span>' ;
echo '</li>' ;
}
echo '</ol>' ;
}
echo '<p><a class="button button-secondary" href="' . esc_url ( admin_url ( 'admin.php?page=' . self :: MENU_SLUG_STATS )) . '">' . esc_html__ ( 'Open full statistics' , 'ansico-stat-plugin' ) . '</a></p>' ;
echo '</div>' ;
}
protected function render_chart_markup ( array $chart_data , string $chart_id , array $args = []) {
$labels = [];
$values = [];
foreach ( $chart_data as $row ) {
$labels [] = wp_date ( get_option ( 'date_format' ), strtotime ( $row [ 'date' ]));
$values [] = ( int ) ( $row [ 'views' ] ? ? 0 );
}
if ( empty ( $chart_data )) {
return '<p>' . esc_html__ ( 'No tracked data for this period.' , 'ansico-stat-plugin' ) . '</p>' ;
}
$max = max ( $values ? : [ 0 ]);
$sum = array_sum ( $values );
if ( $sum <= 0 ) {
return '<p>' . esc_html__ ( 'No tracked data for this period.' , 'ansico-stat-plugin' ) . '</p>' ;
}
$is_widget = ! empty ( $args [ 'is_widget' ]);
$height = $is_widget ? 250 : 220 ;
$width = $is_widget ? 920 : 860 ;
$chart_top = $is_widget ? 12 : 20 ;
$chart_bottom = $is_widget ? 198 : 185 ;
$chart_left = $is_widget ? 44 : 26 ;
$chart_right = $is_widget ? 888 : 830 ;
$count = count ( $values );
$stepX = $count > 1 ? ( $chart_right - $chart_left ) / ( $count - 1 ) : ( $chart_right - $chart_left );
$points = [];
$area_points = [];
$circles = [];
foreach ( $values as $index => $value ) {
$x = $chart_left + ( $stepX * $index );
$y = $chart_bottom ;
if ( $max > 0 ) {
$y = $chart_top + (( $chart_bottom - $chart_top ) * ( 1 - ( $value / $max )));
}
$points [] = round ( $x , 2 ) . ',' . round ( $y , 2 );
$area_points [] = round ( $x , 2 ) . ',' . round ( $y , 2 );
$circles [] = [
'x' => round ( $x , 2 ),
'y' => round ( $y , 2 ),
'label' => ( string ) $labels [ $index ],
'value' => ( int ) $value ,
];
}
$area_path = '' ;
if ( ! empty ( $area_points )) {
$area_path = $chart_left . ',' . $chart_bottom . ' ' . implode ( ' ' , $area_points ) . ' ' . $chart_right . ',' . $chart_bottom ;
}
$y_ticks = [];
$tick_count = 5 ;
for ( $i = 0 ; $i < $tick_count ; $i ++ ) {
$ratio = $tick_count > 1 ? $i / ( $tick_count - 1 ) : 0 ;
$value = ( int ) round ( $max * ( 1 - $ratio ));
$y = $chart_top + (( $chart_bottom - $chart_top ) * $ratio );
$y_ticks [] = [
'value' => $value ,
'y' => round ( $y , 2 ),
];
}
ob_start ();
?>
< div class = " ansico-stat-chart-wrap ansico-stat-chart-wrap-enhanced <?php echo $is_widget ? 'ansico-stat-chart-wrap-widget' : ''; ?> " id = " <?php echo esc_attr( $chart_id ); ?>-wrap " >
< div class = " ansico-stat-line-chart ansico-stat-line-chart-enhanced <?php echo $is_widget ? 'ansico-stat-line-chart-widget' : ''; ?> " id = " <?php echo esc_attr( $chart_id ); ?> " >
< svg viewBox = " 0 0 <?php echo esc_attr((string) $width ); ?> <?php echo esc_attr((string) $height ); ?> " width = " 100% " height = " <?php echo esc_attr((string) $height ); ?> " role = " img " aria - label = " <?php echo esc_attr__('Views chart', 'ansico-stat-plugin'); ?> " >
< defs >
< linearGradient id = " <?php echo esc_attr( $chart_id . '-gradient'); ?> " x1 = " 0 " y1 = " 0 " x2 = " 0 " y2 = " 1 " >
< stop offset = " 0% " stop - color = " #2271b1 " stop - opacity = " 0.24 " ></ stop >
< stop offset = " 100% " stop - color = " #2271b1 " stop - opacity = " 0.03 " ></ stop >
</ linearGradient >
</ defs >
< ? php foreach ( $y_ticks as $tick ) : ?>
< line x1 = " <?php echo esc_attr((string) $chart_left ); ?> " y1 = " <?php echo esc_attr((string) $tick['y'] ); ?> " x2 = " <?php echo esc_attr((string) $chart_right ); ?> " y2 = " <?php echo esc_attr((string) $tick['y'] ); ?> " stroke = " #e5e7eb " stroke - width = " 1 " ></ line >
< text x = " 0 " y = " <?php echo esc_attr((string) ( $tick['y'] + 4)); ?> " font - size = " <?php echo esc_attr( $is_widget ? '13' : '11'); ?> " fill = " #6b7280 " >< ? php echo esc_html ( number_format_i18n (( int ) $tick [ 'value' ])); ?> </text>
< ? php endforeach ; ?>
< line x1 = " <?php echo esc_attr((string) $chart_left ); ?> " y1 = " <?php echo esc_attr((string) $chart_bottom ); ?> " x2 = " <?php echo esc_attr((string) $chart_right ); ?> " y2 = " <?php echo esc_attr((string) $chart_bottom ); ?> " stroke = " #cbd5e1 " stroke - width = " 1.2 " ></ line >
< polygon fill = " url(#<?php echo esc_attr( $chart_id . '-gradient'); ?>) " points = " <?php echo esc_attr( $area_path ); ?> " ></ polygon >
< polyline fill = " none " stroke = " #2271b1 " stroke - width = " 3 " stroke - linecap = " round " stroke - linejoin = " round " points = " <?php echo esc_attr(implode(' ', $points )); ?> " ></ polyline >
< ? php foreach ( $circles as $point ) : ?>
< rect class = " ansico-stat-chart-point-hit " x = " <?php echo esc_attr((string) ( $point['x'] - 18)); ?> " y = " <?php echo esc_attr((string) ( $point['y'] - 18)); ?> " width = " 36 " height = " 36 " rx = " 18 " ry = " 18 " fill = " transparent " data - label = " <?php echo esc_attr( $point['label'] ); ?> " data - value = " <?php echo esc_attr(number_format_i18n( $point['value'] )); ?> " ></ rect >
< circle class = " ansico-stat-chart-point " cx = " <?php echo esc_attr((string) $point['x'] ); ?> " cy = " <?php echo esc_attr((string) $point['y'] ); ?> " r = " 3.5 " fill = " #ffffff " stroke = " #2271b1 " stroke - width = " 2.5 " ></ circle >
< ? php endforeach ; ?>
< ? php
$label_every = max ( 1 , ( int ) ceil ( $count / 8 ));
foreach ( $circles as $index => $point ) :
if ( $index % $label_every !== 0 && $index !== ( $count - 1 )) {
continue ;
}
?>
< text x = " <?php echo esc_attr((string) $point['x'] ); ?> " y = " <?php echo esc_attr((string) ( $is_widget ? 226 : 205)); ?> " text - anchor = " middle " font - size = " <?php echo esc_attr( $is_widget ? '13' : '11'); ?> " fill = " #6b7280 " >< ? php echo esc_html ( $point [ 'label' ]); ?> </text>
< ? php endforeach ; ?>
</ svg >
</ div >
< div class = " ansico-stat-chart-tooltip " style = " display:none; left:-9999px; top:-9999px; opacity:0; " aria - hidden = " true " ></ div >
</ div >
< script >
( function () {
var root = document . getElementById ( < ? php echo wp_json_encode ( $chart_id ); ?> );
var wrap = document . getElementById ( < ? php echo wp_json_encode ( $chart_id . '-wrap' ); ?> );
if ( ! root || ! wrap ) return ;
var tooltip = wrap . querySelector ( '.ansico-stat-chart-tooltip' );
var points = root . querySelectorAll ( '.ansico-stat-chart-point-hit' );
if ( ! tooltip || ! points . length ) return ;
function resetTooltip () {
tooltip . style . display = 'none' ;
tooltip . style . opacity = '0' ;
tooltip . style . left = '-9999px' ;
tooltip . style . top = '-9999px' ;
}
function positionTooltipForPoint ( point ) {
var wrapRect = wrap . getBoundingClientRect ();
var pointRect = point . getBoundingClientRect ();
var tipRect = tooltip . getBoundingClientRect ();
var pointCenterX = ( pointRect . left - wrapRect . left ) + ( pointRect . width / 2 );
var pointTopY = ( pointRect . top - wrapRect . top );
var left = pointCenterX - ( tipRect . width / 2 );
var top = pointTopY - tipRect . height - 10 ;
if ( left + tipRect . width > wrapRect . width - 8 ) left = wrapRect . width - tipRect . width - 8 ;
if ( left < 8 ) left = 8 ;
if ( top < 8 ) top = pointTopY + 18 ;
tooltip . style . left = left + 'px' ;
tooltip . style . top = top + 'px' ;
}
function showTooltip ( event ) {
var point = event . currentTarget ;
tooltip . textContent = point . getAttribute ( 'data-label' ) + ': ' + point . getAttribute ( 'data-value' );
tooltip . style . display = 'block' ;
tooltip . style . opacity = '1' ;
positionTooltipForPoint ( point );
}
points . forEach ( function ( point ) {
point . addEventListener ( 'mouseenter' , showTooltip );
point . addEventListener ( 'mouseleave' , resetTooltip );
});
wrap . addEventListener ( 'mouseleave' , resetTooltip );
resetTooltip ();
})();
</ script >
< ? php
return ob_get_clean ();
}
protected function render_bar_chart_markup ( array $chart_data , string $chart_id , string $aria_label = '' ) {
if ( $aria_label === '' ) {
$aria_label = __ ( 'Chart' , 'ansico-stat-plugin' );
}
$values = [];
foreach ( $chart_data as $row ) {
$values [] = ( int ) ( $row [ 'views' ] ? ? $row [ 'total_views' ] ? ? 0 );
}
$max = max ( $values ? : [ 0 ]);
$sum = array_sum ( $values );
ob_start ();
?>
< div class = " ansico-stat-bar-chart ansico-stat-bar-chart-wrap " id = " <?php echo esc_attr( $chart_id ); ?> " >
< ? php if ( empty ( $chart_data ) || $sum <= 0 ) : ?>
< p >< ? php echo esc_html__ ( 'No tracked data for this period.' , 'ansico-stat-plugin' ); ?> </p>
< ? php else : ?>
< div class = " ansico-stat-bar-chart-grid " >
< ? php foreach ( $chart_data as $row ) :
$value = ( int ) ( $row [ 'views' ] ? ? $row [ 'total_views' ] ? ? 0 );
$label = ( string ) ( $row [ 'label' ] ? ? $row [ 'date' ] ? ? $row [ 'dimension_label' ] ? ? $row [ 'dimension_value' ] ? ? '' );
$height = ( $max > 0 && $value > 0 ) ? max ( 6 , ( int ) round (( $value / $max ) * 140 )) : 0 ;
?>
< div class = " ansico-stat-bar-item " >
< div class = " ansico-stat-bar " style = " height:<?php echo esc_attr((string) $height ); ?>px; " data - label = " <?php echo esc_attr( $label ); ?> " data - value = " <?php echo esc_attr(number_format_i18n( $value )); ?> " ></ div >
< div class = " ansico-stat-bar-value " >< ? php echo esc_html ( number_format_i18n ( $value )); ?> </div>
< div class = " ansico-stat-bar-label " >< ? php echo esc_html ( $label ); ?> </div>
</ div >
< ? php endforeach ; ?>
</ div >
< div class = " ansico-stat-chart-tooltip " style = " display:none; left:-9999px; top:-9999px; opacity:0; " aria - hidden = " true " ></ div >
< script >
( function () {
var root = document . getElementById ( < ? php echo wp_json_encode ( $chart_id ); ?> );
if ( ! root ) return ;
var tooltip = root . querySelector ( '.ansico-stat-chart-tooltip' );
var items = root . querySelectorAll ( '.ansico-stat-bar' );
if ( ! tooltip || ! items . length ) return ;
function resetTooltip () {
tooltip . style . display = 'none' ;
tooltip . style . opacity = '0' ;
tooltip . style . left = '-9999px' ;
tooltip . style . top = '-9999px' ;
}
function positionTooltipForBar ( item ) {
var rootRect = root . getBoundingClientRect ();
var itemRect = item . getBoundingClientRect ();
var tipRect = tooltip . getBoundingClientRect ();
var centerX = ( itemRect . left - rootRect . left ) + ( itemRect . width / 2 );
var topY = ( itemRect . top - rootRect . top );
var left = centerX - ( tipRect . width / 2 );
var top = topY - tipRect . height - 10 ;
if ( left + tipRect . width > rootRect . width - 8 ) left = rootRect . width - tipRect . width - 8 ;
if ( left < 8 ) left = 8 ;
if ( top < 8 ) top = topY + 18 ;
tooltip . style . left = left + 'px' ;
tooltip . style . top = top + 'px' ;
}
function showTooltip ( event ) {
var item = event . currentTarget ;
tooltip . textContent = item . getAttribute ( 'data-label' ) + ': ' + item . getAttribute ( 'data-value' );
tooltip . style . display = 'block' ;
tooltip . style . opacity = '1' ;
positionTooltipForBar ( item );
}
items . forEach ( function ( item ) {
item . addEventListener ( 'mouseenter' , showTooltip );
item . addEventListener ( 'mouseleave' , resetTooltip );
});
root . addEventListener ( 'mouseleave' , resetTooltip );
resetTooltip ();
})();
</ script >
< ? php endif ; ?>
</ div >
< ? php
return ob_get_clean ();
}
protected function render_pie_chart_markup ( array $segments , string $chart_id , string $aria_label = '' ) {
if ( $aria_label === '' ) {
$aria_label = __ ( 'Pie chart' , 'ansico-stat-plugin' );
}
$filtered = [];
foreach ( $segments as $label => $value ) {
$value = ( int ) $value ;
if ( $value > 0 ) {
$filtered [( string ) $label ] = $value ;
}
}
if ( empty ( $filtered )) {
return '<p>' . esc_html__ ( 'No tracked data for this period.' , 'ansico-stat-plugin' ) . '</p>' ;
}
$total = array_sum ( $filtered );
$palette = [ '#2271b1' , '#72aee6' , '#135e96' , '#8c8f94' , '#dba617' , '#00a32a' , '#d63638' , '#50575e' ];
$cx = 90 ;
$cy = 90 ;
$r = 72 ;
$startAngle = - 90 ;
$index = 0 ;
ob_start ();
?>
< div class = " ansico-stat-pie-wrap " id = " <?php echo esc_attr( $chart_id ); ?> " >
< div class = " ansico-stat-pie-chart-shell " >
< svg viewBox = " 0 0 260 180 " width = " 100% " height = " 180 " role = " img " aria - label = " <?php echo esc_attr( $aria_label ); ?> " >
< ? php foreach ( $filtered as $label => $value ) :
$angle = ( $value / $total ) * 360 ;
$endAngle = $startAngle + $angle ;
$x1 = $cx + $r * cos ( deg2rad ( $startAngle ));
$y1 = $cy + $r * sin ( deg2rad ( $startAngle ));
$x2 = $cx + $r * cos ( deg2rad ( $endAngle ));
$y2 = $cy + $r * sin ( deg2rad ( $endAngle ));
$largeArc = $angle > 180 ? 1 : 0 ;
$color = $palette [ $index % count ( $palette )];
$percent = $total > 0 ? number_format_i18n (( $value / $total ) * 100 , 1 ) . '%' : '0%' ;
?>
< path class = " ansico-stat-pie-segment " d = " M <?php echo esc_attr((string) $cx ); ?> <?php echo esc_attr((string) $cy ); ?> L <?php echo esc_attr(round( $x1 , 3)); ?> <?php echo esc_attr(round( $y1 , 3)); ?> A <?php echo esc_attr((string) $r ); ?> <?php echo esc_attr((string) $r ); ?> 0 <?php echo esc_attr((string) $largeArc ); ?> 1 <?php echo esc_attr(round( $x2 , 3)); ?> <?php echo esc_attr(round( $y2 , 3)); ?> Z " fill = " <?php echo esc_attr( $color ); ?> " data - label = " <?php echo esc_attr( $label ); ?> " data - value = " <?php echo esc_attr(number_format_i18n( $value )); ?> " data - percent = " <?php echo esc_attr( $percent ); ?> " ></ path >
< ? php
$startAngle = $endAngle ;
$index ++ ;
endforeach ; ?>
< circle cx = " <?php echo esc_attr((string) $cx ); ?> " cy = " <?php echo esc_attr((string) $cy ); ?> " r = " 32 " fill = " #fff " ></ circle >
< text x = " <?php echo esc_attr((string) $cx ); ?> " y = " <?php echo esc_attr((string) ( $cy + 4)); ?> " text - anchor = " middle " font - size = " 12 " >< ? php echo esc_html ( number_format_i18n ( $total )); ?> </text>
</ svg >
< div class = " ansico-stat-chart-tooltip " style = " display:none; left:-9999px; top:-9999px; opacity:0; " aria - hidden = " true " ></ div >
</ div >
< div class = " ansico-stat-pie-legend " >
< ? php $index = 0 ; foreach ( $filtered as $label => $value ) : $color = $palette [ $index % count ( $palette )]; $percent = $total > 0 ? number_format_i18n (( $value / $total ) * 100 , 1 ) . '%' : '0%' ; ?>
< div class = " ansico-stat-legend-item " >
< span class = " ansico-stat-legend-swatch " style = " background:<?php echo esc_attr( $color ); ?> " ></ span >
< span >< ? php echo esc_html ( $label ); ?> (<?php echo esc_html(number_format_i18n($value)); ?> · <?php echo esc_html($percent); ?>)</span>
</ div >
< ? php $index ++ ; endforeach ; ?>
</ div >
</ div >
< script >
( function () {
var root = document . getElementById ( < ? php echo wp_json_encode ( $chart_id ); ?> );
if ( ! root ) return ;
var tooltip = root . querySelector ( '.ansico-stat-chart-tooltip' );
var segments = root . querySelectorAll ( '.ansico-stat-pie-segment' );
var host = root . querySelector ( '.ansico-stat-pie-chart-shell' );
if ( ! tooltip || ! segments . length || ! host ) return ;
function resetTooltip () {
tooltip . style . display = 'none' ;
tooltip . style . opacity = '0' ;
tooltip . style . left = '-9999px' ;
tooltip . style . top = '-9999px' ;
}
function positionTooltip ( event ) {
var rect = host . getBoundingClientRect ();
var tipRect = tooltip . getBoundingClientRect ();
var x = event . clientX - rect . left ;
var y = event . clientY - rect . top ;
var left = x - ( tipRect . width / 2 );
var top = y - tipRect . height - 12 ;
if ( left + tipRect . width > rect . width - 8 ) left = rect . width - tipRect . width - 8 ;
if ( left < 8 ) left = 8 ;
if ( top < 8 ) top = y + 16 ;
tooltip . style . left = left + 'px' ;
tooltip . style . top = top + 'px' ;
}
function showTooltip ( event ) {
var segment = event . currentTarget ;
tooltip . textContent = segment . getAttribute ( 'data-label' ) + ': ' + segment . getAttribute ( 'data-value' ) + ' (' + segment . getAttribute ( 'data-percent' ) + ')' ;
tooltip . style . display = 'block' ;
tooltip . style . opacity = '1' ;
positionTooltip ( event );
}
segments . forEach ( function ( segment ) {
segment . addEventListener ( 'mouseenter' , showTooltip );
segment . addEventListener ( 'mousemove' , function ( event ) {
if ( tooltip . style . display === 'none' ) return ;
positionTooltip ( event );
});
segment . addEventListener ( 'mouseleave' , resetTooltip );
});
host . addEventListener ( 'mouseleave' , resetTooltip );
resetTooltip ();
})();
</ script >
< ? php
return ob_get_clean ();
}
protected function date_days_ago ( int $days ) {
return wp_date ( 'Y-m-d' , strtotime ( '-' . max ( 0 , $days ) . ' days' , current_time ( 'timestamp' )));
}
protected function get_daily_chart_data ( int $days = 30 ) {
global $wpdb ;
$days = max ( 1 , $days );
$daily_table = self :: daily_table_name ();
$start = $this -> date_days_ago ( $days - 1 );
$end = current_time ( 'Y-m-d' );
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, views FROM { $daily_table } WHERE stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
$start ,
$end
), ARRAY_A );
$indexed = [];
foreach ( $rows as $row ) {
$indexed [ $row [ 'stat_date' ]] = ( int ) $row [ 'views' ];
}
$data = [];
$cursor = strtotime ( $start );
$end_ts = strtotime ( $end );
while ( $cursor <= $end_ts ) {
$date = gmdate ( 'Y-m-d' , $cursor );
$data [] = [
'date' => $date ,
'views' => $indexed [ $date ] ? ? 0 ,
];
$cursor = strtotime ( '+1 day' , $cursor );
}
return $data ;
}
protected function get_install_month_key () {
$install_date = get_option ( self :: INSTALL_OPTION_KEY , current_time ( 'Y-m-d' ));
$timestamp = strtotime ( $install_date ? : current_time ( 'Y-m-d' ));
return wp_date ( 'Y-m' , $timestamp ? : current_time ( 'timestamp' ));
}
protected function get_install_year () {
$install_date = get_option ( self :: INSTALL_OPTION_KEY , current_time ( 'Y-m-d' ));
$timestamp = strtotime ( $install_date ? : current_time ( 'Y-m-d' ));
return ( int ) wp_date ( 'Y' , $timestamp ? : current_time ( 'timestamp' ));
}
protected function get_selected_month () {
$requested = isset ( $_GET [ 'month' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'month' ])) : '' ;
if ( $requested && preg_match ( '/^\d{4}-\d{2}$/' , $requested )) {
return $requested ;
}
return wp_date ( 'Y-m' , current_time ( 'timestamp' ));
}
protected function get_selected_year () {
$requested = isset ( $_GET [ 'year' ]) ? absint ( wp_unslash ( $_GET [ 'year' ])) : 0 ;
if ( $requested >= 1970 && $requested <= 9999 ) {
return $requested ;
}
return ( int ) wp_date ( 'Y' , current_time ( 'timestamp' ));
}
protected function render_month_navigation ( string $selected_month ) {
$selected_ts = strtotime ( $selected_month . '-01' );
$current_ts = strtotime ( wp_date ( 'Y-m-01' , current_time ( 'timestamp' )));
$prev_month = wp_date ( 'Y-m' , strtotime ( '-1 month' , $selected_ts ));
$next_month = wp_date ( 'Y-m' , strtotime ( '+1 month' , $selected_ts ));
$base_url = admin_url ( 'admin.php?page=' . self :: MENU_SLUG_STATS );
echo '<div class="ansico-stat-nav" style="display:flex;justify-content:space-between;align-items:center;gap:12px;margin:0 0 1rem;">' ;
echo '<a class="button button-secondary" href="' . esc_url ( add_query_arg ([ 'month' => $prev_month , 'year' => ( int ) wp_date ( 'Y' , $selected_ts )], $base_url )) . '">← ' . esc_html__ ( 'Older month' , 'ansico-stat-plugin' ) . '</a>' ;
echo '<strong>' . esc_html ( wp_date ( 'F Y' , $selected_ts )) . '</strong>' ;
if ( $selected_ts < $current_ts ) {
echo '<a class="button button-secondary" href="' . esc_url ( add_query_arg ([ 'month' => $next_month , 'year' => ( int ) wp_date ( 'Y' , strtotime ( $next_month . '-01' ))], $base_url )) . '">' . esc_html__ ( 'Newer month' , 'ansico-stat-plugin' ) . ' →</a>' ;
} else {
echo '<span></span>' ;
}
echo '</div>' ;
}
protected function render_year_navigation ( int $selected_year ) {
$current_year = ( int ) wp_date ( 'Y' , current_time ( 'timestamp' ));
$current_page = isset ( $_GET [ 'page' ]) ? sanitize_key (( string ) wp_unslash ( $_GET [ 'page' ])) : self :: MENU_SLUG_STATS ;
$target_slug = $current_page === self :: MENU_SLUG_YEARLY ? self :: MENU_SLUG_YEARLY : self :: MENU_SLUG_STATS ;
$base_url = admin_url ( 'admin.php?page=' . $target_slug );
echo '<div class="ansico-stat-nav" style="display:flex;justify-content:space-between;align-items:center;gap:12px;margin:0 0 1rem;">' ;
echo '<a class="button button-secondary" href="' . esc_url ( add_query_arg ([ 'year' => $selected_year - 1 , 'month' => $this -> get_selected_month ()], $base_url )) . '">← ' . esc_html__ ( 'Older year' , 'ansico-stat-plugin' ) . '</a>' ;
echo '<strong>' . esc_html (( string ) $selected_year ) . '</strong>' ;
if ( $selected_year < $current_year ) {
echo '<a class="button button-secondary" href="' . esc_url ( add_query_arg ([ 'year' => $selected_year + 1 , 'month' => $this -> get_selected_month ()], $base_url )) . '">' . esc_html__ ( 'Newer year' , 'ansico-stat-plugin' ) . ' →</a>' ;
} else {
echo '<span></span>' ;
}
echo '</div>' ;
}
protected function get_top_posts_in_period ( string $start , string $end , int $limit = 10 ) {
global $wpdb ;
$table = self :: post_daily_table_name ();
$results = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT post_id, SUM(views) AS period_views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
GROUP BY post_id
ORDER BY period_views DESC
LIMIT % d " ,
$start ,
$end ,
$limit
), ARRAY_A );
return is_array ( $results ) ? $results : [];
}
protected function get_monthly_top_posts ( string $month_key , int $limit = 10 ) {
$start = $month_key . '-01' ;
$end = wp_date ( 'Y-m-t' , strtotime ( $start ));
return $this -> get_top_posts_in_period ( $start , $end , $limit );
}
protected function get_yearly_top_posts ( int $year , int $limit = 10 ) {
$start = sprintf ( '%04d-01-01' , $year );
$end = sprintf ( '%04d-12-31' , $year );
return $this -> get_top_posts_in_period ( $start , $end , $limit );
}
protected function get_latest_month_range () {
return [
'start' => wp_date ( 'Y-m-01' , current_time ( 'timestamp' )),
'end' => wp_date ( 'Y-m-t' , current_time ( 'timestamp' )),
];
}
protected function get_post_views_for_latest_month ( int $post_id ) {
global $wpdb ;
$range = $this -> get_latest_month_range ();
$table = self :: post_daily_table_name ();
$views = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(views)
FROM { $table }
WHERE post_id = % d
AND stat_date BETWEEN % s AND % s " ,
$post_id ,
$range [ 'start' ],
$range [ 'end' ]
));
return ( int ) $views ;
}
protected function get_referral_summary_for_month ( string $month_key ) {
global $wpdb ;
$table = self :: referral_table_name ();
$start = $month_key . '-01' ;
$end = wp_date ( 'Y-m-t' , strtotime ( $start ));
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT category, SUM(visits) AS total_visits
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
GROUP BY category " ,
$start ,
$end
), ARRAY_A );
$summary = [
'direct' => 0 ,
'search' => 0 ,
'social' => 0 ,
'website' => 0 ,
];
foreach ( $rows as $row ) {
$category = isset ( $row [ 'category' ]) ? ( string ) $row [ 'category' ] : '' ;
if ( isset ( $summary [ $category ])) {
$summary [ $category ] = ( int ) $row [ 'total_visits' ];
}
}
return $summary ;
}
protected function get_referral_sources_for_month ( string $month_key , string $category , int $limit = 50 ) {
global $wpdb ;
$table = self :: referral_table_name ();
$start = $month_key . '-01' ;
$end = wp_date ( 'Y-m-t' , strtotime ( $start ));
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT source_url, source_host, SUM(visits) AS total_visits
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
AND category = % s
AND source_url <> ''
GROUP BY source_url , source_host
ORDER BY total_visits DESC , source_host ASC
LIMIT % d " ,
$start ,
$end ,
$category ,
$limit
), ARRAY_A );
return is_array ( $rows ) ? $rows : [];
}
protected function get_top_404_pages_for_month ( string $month_key , int $limit = 10 ) {
global $wpdb ;
$table = self :: page_daily_table_name ();
$start = $month_key . '-01' ;
$end = wp_date ( 'Y-m-t' , strtotime ( $start ));
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT page_key, page_label, page_url, SUM(views) AS period_views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
AND page_type = % s
GROUP BY page_key , page_label , page_url
ORDER BY period_views DESC , page_url ASC
LIMIT % d " ,
$start ,
$end ,
'404' ,
$limit
), ARRAY_A );
return is_array ( $rows ) ? $rows : [];
}
protected function get_dimension_rows_for_month ( string $month_key , string $dimension_type , int $limit = 10 ) {
global $wpdb ;
$table = self :: dimension_table_name ();
$start = $month_key . '-01' ;
$end = wp_date ( 'Y-m-t' , strtotime ( $start ));
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT dimension_value, SUM(views) AS total_views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
AND dimension_type = % s
GROUP BY dimension_value
ORDER BY total_views DESC , dimension_value ASC
LIMIT % d " ,
$start ,
$end ,
$dimension_type ,
$limit
), ARRAY_A );
if ( ! is_array ( $rows )) {
return [];
}
if ( $dimension_type === 'country' ) {
foreach ( $rows as & $row ) {
$row [ 'dimension_label' ] = $this -> get_country_label (( string ) ( $row [ 'dimension_value' ] ? ? '' ));
}
unset ( $row );
}
return $rows ;
}
protected function get_dimension_rows_for_year ( int $year , string $dimension_type , int $limit = 10 ) {
global $wpdb ;
$table = self :: dimension_table_name ();
$start = sprintf ( '%04d-01-01' , $year );
$end = sprintf ( '%04d-12-31' , $year );
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT dimension_value, SUM(views) AS total_views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
AND dimension_type = % s
GROUP BY dimension_value
ORDER BY total_views DESC , dimension_value ASC
LIMIT % d " ,
$start ,
$end ,
$dimension_type ,
$limit
), ARRAY_A );
if ( ! is_array ( $rows )) {
return [];
}
if ( $dimension_type === 'country' ) {
foreach ( $rows as & $row ) {
$row [ 'dimension_label' ] = $this -> get_country_label (( string ) ( $row [ 'dimension_value' ] ? ? '' ));
}
unset ( $row );
}
return $rows ;
}
protected function get_referral_summary_for_year ( int $year ) {
global $wpdb ;
$table = self :: referral_table_name ();
$start = sprintf ( '%04d-01-01' , $year );
$end = sprintf ( '%04d-12-31' , $year );
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT category, SUM(visits) AS total_visits
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
GROUP BY category " ,
$start ,
$end
), ARRAY_A );
$summary = [
'Direct' => 0 ,
'Search' => 0 ,
'Social' => 0 ,
'Other websites' => 0 ,
];
foreach ( $rows as $row ) {
$category = ( string ) ( $row [ 'category' ] ? ? '' );
$value = ( int ) ( $row [ 'total_visits' ] ? ? 0 );
if ( $category === 'direct' ) {
$summary [ 'Direct' ] = $value ;
} elseif ( $category === 'search' ) {
$summary [ 'Search' ] = $value ;
} elseif ( $category === 'social' ) {
$summary [ 'Social' ] = $value ;
} elseif ( $category === 'website' ) {
$summary [ 'Other websites' ] = $value ;
}
}
return $summary ;
}
protected function get_monthly_totals_for_year ( int $year ) {
global $wpdb ;
$table = self :: daily_table_name ();
$start = sprintf ( '%04d-01-01' , $year );
$end = sprintf ( '%04d-12-31' , $year );
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
ORDER BY stat_date ASC " ,
$start ,
$end
), ARRAY_A );
$map = [];
for ( $month = 1 ; $month <= 12 ; $month ++ ) {
$month_key = sprintf ( '%04d-%02d' , $year , $month );
$map [ $month_key ] = 0 ;
}
if ( is_array ( $rows )) {
foreach ( $rows as $row ) {
$stat_date = isset ( $row [ 'stat_date' ]) ? ( string ) $row [ 'stat_date' ] : '' ;
$month_key = substr ( $stat_date , 0 , 7 );
if ( $month_key !== '' && isset ( $map [ $month_key ])) {
$map [ $month_key ] += ( int ) ( $row [ 'views' ] ? ? 0 );
}
}
}
$data = [];
foreach ( $map as $month_key => $views ) {
$data [] = [
'label' => wp_date ( 'M' , strtotime ( $month_key . '-01' )),
'views' => ( int ) $views ,
];
}
return $data ;
}
protected function is_unknown_only_dimension_rows ( array $rows ) {
if ( empty ( $rows )) {
return true ;
}
foreach ( $rows as $row ) {
$value = strtolower ( trim (( string ) ( $row [ 'dimension_value' ] ? ? '' )));
$label = strtolower ( trim (( string ) ( $row [ 'dimension_label' ] ? ? '' )));
if ( ! in_array ( $value , [ '' , 'unknown' ], true ) && ! in_array ( $label , [ '' , 'unknown' ], true )) {
return false ;
}
}
return true ;
}
protected function format_percent ( int $value , int $total ) {
if ( $total <= 0 ) {
return '0%' ;
}
$percent = ( $value / $total ) * 100 ;
return number_format_i18n ( $percent , 1 ) . '%' ;
}
protected function render_referral_summary_table ( array $summary ) {
$total = array_sum ( array_map ( 'intval' , $summary ));
?>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( 'Referral type' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Visits' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Percent' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< tr >< td >< ? php echo esc_html__ ( 'Direct visitors' , 'ansico-stat-plugin' ); ?> </td><td><?php echo esc_html(number_format_i18n((int) ($summary['direct'] ?? 0))); ?></td><td><?php echo esc_html($this->format_percent((int) ($summary['direct'] ?? 0), $total)); ?></td></tr>
< tr >< td >< ? php echo esc_html__ ( 'Visitors from search engines' , 'ansico-stat-plugin' ); ?> </td><td><?php echo esc_html(number_format_i18n((int) ($summary['search'] ?? 0))); ?></td><td><?php echo esc_html($this->format_percent((int) ($summary['search'] ?? 0), $total)); ?></td></tr>
< tr >< td >< ? php echo esc_html__ ( 'Visitors from social media' , 'ansico-stat-plugin' ); ?> </td><td><?php echo esc_html(number_format_i18n((int) ($summary['social'] ?? 0))); ?></td><td><?php echo esc_html($this->format_percent((int) ($summary['social'] ?? 0), $total)); ?></td></tr>
< tr >< td >< ? php echo esc_html__ ( 'Visitors from other websites' , 'ansico-stat-plugin' ); ?> </td><td><?php echo esc_html(number_format_i18n((int) ($summary['website'] ?? 0))); ?></td><td><?php echo esc_html($this->format_percent((int) ($summary['website'] ?? 0), $total)); ?></td></tr>
</ tbody >
</ table >
< ? php
}
protected function render_dimension_table ( array $rows , string $value_label , string $views_label = '' ) {
if ( $views_label === '' ) {
$views_label = __ ( 'Views' , 'ansico-stat-plugin' );
}
if ( empty ( $rows )) {
return ;
}
$total = 0 ;
foreach ( $rows as $row ) {
$total += ( int ) ( $row [ 'total_views' ] ? ? 0 );
}
?>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( '#' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html ( $value_label ); ?> </th>
< th >< ? php echo esc_html ( $views_label ); ?> </th>
< th >< ? php echo esc_html__ ( 'Percent' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< ? php foreach ( $rows as $index => $row ) : ?>
< ? php $count = ( int ) ( $row [ 'total_views' ] ? ? 0 ); ?>
< tr >
< td >< ? php echo esc_html (( string ) ( $index + 1 )); ?> </td>
< td >
< ? php
$label = ( string ) ( $row [ 'dimension_label' ] ? ? $row [ 'dimension_value' ] ? ? '' );
$value = ( string ) ( $row [ 'dimension_value' ] ? ? '' );
echo esc_html ( $label );
if ( ! empty ( $value ) && $label !== $value ) {
echo ' <span class="description">(' . esc_html ( $value ) . ')</span>' ;
}
?>
</ td >
< td >< ? php echo esc_html ( number_format_i18n ( $count )); ?> </td>
< td >< ? php echo esc_html ( $this -> format_percent ( $count , $total )); ?> </td>
</ tr >
< ? php endforeach ; ?>
</ tbody >
</ table >
< ? php
}
protected function render_top_404_table ( array $rows ) {
if ( empty ( $rows )) {
echo '<p>' . esc_html__ ( 'No tracked 404 views for this month.' , 'ansico-stat-plugin' ) . '</p>' ;
return ;
}
?>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( '#' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( '404 URL' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Views' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< ? php foreach ( $rows as $index => $row ) : ?>
< tr >
< td >< ? php echo esc_html (( string ) ( $index + 1 )); ?> </td>
< td >< ? php echo $this -> format_external_link (( string ) ( $row [ 'page_url' ] ? ? '' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $row [ 'period_views' ] ? ? 0 ))); ?> </td>
</ tr >
< ? php endforeach ; ?>
</ tbody >
</ table >
< ? php
}
protected function render_top_posts_table ( array $rows , string $views_label ) {
if ( empty ( $rows )) {
echo '<p>' . esc_html__ ( 'No views tracked for this period.' , 'ansico-stat-plugin' ) . '</p>' ;
return ;
}
?>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( '#' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Title' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Post type' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html ( $views_label ); ?> </th>
< th >< ? php echo esc_html__ ( 'Total lifetime views' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< ? php foreach ( $rows as $index => $row ) : ?>
< ? php
$post_id = ( int ) $row [ 'post_id' ];
$title = get_the_title ( $post_id ) ? : __ ( '(no title)' , 'ansico-stat-plugin' );
$total_views = ( int ) get_post_meta ( $post_id , self :: TOTAL_META_KEY , true );
$post_type = get_post_type ( $post_id );
?>
< tr >
< td >< ? php echo esc_html (( string ) ( $index + 1 )); ?> </td>
< td >< ? php echo $this -> format_post_link ( $post_id , $title ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
< td >< ? php echo esc_html ( $post_type ? : '—' ); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) $row [ 'period_views' ])); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n ( $total_views )); ?> </td>
</ tr >
< ? php endforeach ; ?>
</ tbody >
</ table >
< ? php
}
protected function render_referral_sources_table ( array $rows , string $label ) {
echo '<h3>' . esc_html ( $label ) . '</h3>' ;
if ( empty ( $rows )) {
echo '<p>' . esc_html__ ( 'No tracked referral URLs for this category in the selected month.' , 'ansico-stat-plugin' ) . '</p>' ;
return ;
}
?>
< ? php $total = 0 ; foreach ( $rows as $row ) { $total += ( int ) ( $row [ 'total_visits' ] ? ? 0 ); } ?>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( 'Source host' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Source URL' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Visits' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Percent' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< ? php foreach ( $rows as $row ) : ?>
< ? php $count = ( int ) ( $row [ 'total_visits' ] ? ? 0 ); ?>
< tr >
< td >< ? php echo esc_html ( $row [ 'source_host' ] ? : '—' ); ?> </td>
< td >< ? php echo $this -> format_external_link (( string ) $row [ 'source_url' ]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
< td >< ? php echo esc_html ( number_format_i18n ( $count )); ?> </td>
< td >< ? php echo esc_html ( $this -> format_percent ( $count , $total )); ?> </td>
</ tr >
< ? php endforeach ; ?>
</ tbody >
</ table >
< ? php
}
2026-04-17 21:56:44 +00:00
protected function normalize_target_url ( string $url ) {
$url = trim ( $url );
if ( $url === '' ) {
$url = home_url ( '/' );
}
if ( strpos ( $url , 'http://' ) !== 0 && strpos ( $url , 'https://' ) !== 0 ) {
$url = home_url ( '/' . ltrim ( $url , '/' ));
}
$parts = wp_parse_url ( $url );
if ( empty ( $parts [ 'host' ])) {
return '' ;
}
$scheme = ! empty ( $parts [ 'scheme' ]) ? strtolower (( string ) $parts [ 'scheme' ]) : ( is_ssl () ? 'https' : 'http' );
$host = strtolower (( string ) $parts [ 'host' ]);
$path = isset ( $parts [ 'path' ]) ? ( string ) $parts [ 'path' ] : '/' ;
$path = $path === '' ? '/' : $path ;
$normalized_path = untrailingslashit ( $path );
$normalized_path = $normalized_path === '' ? '/' : $normalized_path ;
$normalized = $scheme . '://' . $host . ( $normalized_path === '/' ? '/' : $normalized_path . '/' );
if ( ! empty ( $parts [ 'query' ])) {
$normalized .= '?' . $parts [ 'query' ];
}
return $normalized ;
}
protected function get_requested_single_stats_url () {
$requested = isset ( $_GET [ 'target_url' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'target_url' ])) : '' ;
return $requested !== '' ? $requested : home_url ( '/' );
}
protected function sanitize_stats_date ( string $date ) {
$date = trim ( $date );
if ( $date === '' || ! preg_match ( '/^\d{4}-\d{2}-\d{2}$/' , $date )) {
return '' ;
}
$timestamp = strtotime ( $date . ' 00:00:00' );
if ( ! $timestamp ) {
return '' ;
}
return gmdate ( 'Y-m-d' , $timestamp ) === $date ? $date : '' ;
}
protected function get_single_stats_date_range () {
$default_end = current_time ( 'Y-m-d' );
$default_start = $this -> date_days_ago ( 29 );
$start = isset ( $_GET [ 'start_date' ]) ? $this -> sanitize_stats_date (( string ) wp_unslash ( $_GET [ 'start_date' ])) : '' ;
$end = isset ( $_GET [ 'end_date' ]) ? $this -> sanitize_stats_date (( string ) wp_unslash ( $_GET [ 'end_date' ])) : '' ;
if ( $start === '' ) {
$start = $default_start ;
}
if ( $end === '' ) {
$end = $default_end ;
}
if ( strtotime ( $start ) > strtotime ( $end )) {
[ $start , $end ] = [ $end , $start ];
}
return [
'start' => $start ,
'end' => $end ,
'days' => max ( 1 , ( int ) floor (( strtotime ( $end ) - strtotime ( $start )) / DAY_IN_SECONDS ) + 1 ),
'is_custom' => isset ( $_GET [ 'start_date' ]) || isset ( $_GET [ 'end_date' ]),
];
}
protected function build_single_stats_admin_url ( string $target_url , string $start_date = '' , string $end_date = '' ) {
$args = [
'page' => self :: MENU_SLUG_SINGLE ,
'target_url' => $target_url !== '' ? $target_url : home_url ( '/' ),
];
if ( $start_date !== '' ) {
$args [ 'start_date' ] = $start_date ;
}
if ( $end_date !== '' ) {
$args [ 'end_date' ] = $end_date ;
}
return add_query_arg ( $args , admin_url ( 'admin.php' ));
}
public function register_frontend_admin_bar_link ( $wp_admin_bar ) {
if ( is_admin () || ! is_admin_bar_showing () || ! current_user_can ( 'manage_options' ) || ! $this -> is_trackable_request ()) {
return ;
}
$page_context = $this -> get_current_page_context ();
if ( empty ( $page_context ) || empty ( $page_context [ 'url' ])) {
return ;
}
$target_url = $this -> normalize_target_url (( string ) $page_context [ 'url' ]);
if ( $target_url === '' ) {
return ;
}
$wp_admin_bar -> add_node ([
'id' => 'ansico-stat-single-page' ,
'parent' => 'top-secondary' ,
'title' => '<span class="ab-icon dashicons dashicons-chart-bar" style="font:normal 20px/1 dashicons;top:2px;"></span><span class="ab-label">' . esc_html__ ( 'Page stats' , 'ansico-stat-plugin' ) . '</span>' ,
'href' => esc_url ( $this -> build_single_stats_admin_url ( $target_url )),
'meta' => [
'class' => 'ansico-stat-admin-bar-link' ,
'title' => esc_attr__ ( 'Open statistics for this page' , 'ansico-stat-plugin' ),
],
]);
}
protected function resolve_single_stats_target ( string $url ) {
global $wpdb ;
$normalized_url = $this -> normalize_target_url ( $url );
if ( $normalized_url === '' ) {
return [
'success' => false ,
'message' => __ ( 'Please enter a valid URL.' , 'ansico-stat-plugin' ),
'requested_url' => $url ,
'normalized_url' => '' ,
];
}
$site_host = strtolower (( string ) wp_parse_url ( home_url ( '/' ), PHP_URL_HOST ));
$target_host = strtolower (( string ) wp_parse_url ( $normalized_url , PHP_URL_HOST ));
if ( $site_host === '' || $target_host === '' || preg_replace ( '/^www\./' , '' , $site_host ) !== preg_replace ( '/^www\./' , '' , $target_host )) {
return [
'success' => false ,
'message' => __ ( 'The URL must belong to this WordPress site.' , 'ansico-stat-plugin' ),
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
];
}
$post_id = url_to_postid ( $normalized_url );
if ( $post_id > 0 ) {
$post = get_post ( $post_id );
if ( $this -> is_trackable_post ( $post )) {
return [
'success' => true ,
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
'target' => [
'kind' => 'singular' ,
'post_id' => ( int ) $post_id ,
'post_type' => sanitize_key (( string ) $post -> post_type ),
'page_key' => 'post:' . ( int ) $post_id ,
'page_type' => sanitize_key (( string ) $post -> post_type ),
'object_id' => ( int ) $post_id ,
'label' => get_the_title ( $post_id ) ? : __ ( '(no title)' , 'ansico-stat-plugin' ),
'url' => get_permalink ( $post_id ) ? : $normalized_url ,
'resolved_via' => 'post' ,
],
];
}
}
$front_url = $this -> normalize_target_url ( home_url ( '/' ));
if ( $normalized_url === $front_url ) {
return [
'success' => true ,
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
'target' => [
'kind' => 'archive' ,
'page_key' => 'front_page' ,
'page_type' => 'front_page' ,
'object_id' => 0 ,
'label' => __ ( 'Front page' , 'ansico-stat-plugin' ),
'url' => home_url ( '/' ),
'resolved_via' => 'front_page' ,
],
];
}
$posts_page_id = ( int ) get_option ( 'page_for_posts' );
if ( $posts_page_id > 0 ) {
$posts_page_url = $this -> normalize_target_url (( string ) get_permalink ( $posts_page_id ));
if ( $posts_page_url !== '' && $normalized_url === $posts_page_url ) {
return [
'success' => true ,
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
'target' => [
'kind' => 'archive' ,
'page_key' => 'home' ,
'page_type' => 'home' ,
'object_id' => $posts_page_id ,
'label' => __ ( 'Posts page' , 'ansico-stat-plugin' ),
'url' => get_permalink ( $posts_page_id ) ? : $normalized_url ,
'resolved_via' => 'posts_page' ,
],
];
}
}
$page_daily_table = self :: page_daily_table_name ();
$url_variants = array_values ( array_unique ( array_filter ([
$normalized_url ,
untrailingslashit ( $normalized_url ),
trailingslashit ( $normalized_url ),
])));
if ( ! empty ( $url_variants )) {
$placeholders = implode ( ',' , array_fill ( 0 , count ( $url_variants ), '%s' ));
$sql = " SELECT page_key, page_type, object_id, page_label, page_url \n FROM { $page_daily_table } \n WHERE page_url IN ( { $placeholders } ) \n ORDER BY stat_date DESC \n LIMIT 1 " ;
$row = $wpdb -> get_row ( $wpdb -> prepare ( $sql , $url_variants ), ARRAY_A );
if ( is_array ( $row ) && ! empty ( $row [ 'page_key' ])) {
return [
'success' => true ,
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
'target' => [
'kind' => 'archive' ,
'page_key' => ( string ) $row [ 'page_key' ],
'page_type' => sanitize_key (( string ) ( $row [ 'page_type' ] ? ? 'archive' )),
'object_id' => ! empty ( $row [ 'object_id' ]) ? ( int ) $row [ 'object_id' ] : 0 ,
'label' => sanitize_text_field (( string ) ( $row [ 'page_label' ] ? ? $normalized_url )),
'url' => ! empty ( $row [ 'page_url' ]) ? esc_url_raw (( string ) $row [ 'page_url' ]) : $normalized_url ,
'resolved_via' => 'page_daily' ,
],
];
}
}
return [
'success' => false ,
'message' => __ ( 'No tracked page or post was found for that URL yet. Try opening the page on the site first so the plugin can register it, and then update this report again.' , 'ansico-stat-plugin' ),
'requested_url' => $url ,
'normalized_url' => $normalized_url ,
];
}
protected function get_target_lifetime_views ( array $target ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
return ( int ) get_post_meta (( int ) $target [ 'post_id' ], self :: TOTAL_META_KEY , true );
}
$table = self :: page_daily_table_name ();
$views = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(views) FROM { $table } WHERE page_key = %s " ,
( string ) $target [ 'page_key' ]
));
return ( int ) $views ;
}
protected function get_target_lifetime_unique_visitors ( array $target ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$uniques = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(unique_visitors) FROM { $table } WHERE post_id = %d " ,
( int ) $target [ 'post_id' ]
));
return ( int ) $uniques ;
}
$table = self :: page_daily_table_name ();
$uniques = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(unique_visitors) FROM { $table } WHERE page_key = %s " ,
( string ) $target [ 'page_key' ]
));
return ( int ) $uniques ;
}
protected function get_target_period_unique_visitors ( array $target , string $start , string $end ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$uniques = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(unique_visitors) FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s " ,
( int ) $target [ 'post_id' ],
$start ,
$end
));
return ( int ) $uniques ;
}
$table = self :: page_daily_table_name ();
$uniques = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(unique_visitors) FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s " ,
( string ) $target [ 'page_key' ],
$start ,
$end
));
return ( int ) $uniques ;
}
protected function get_target_daily_chart_data_between ( array $target , string $start , string $end , string $metric = 'views' ) {
global $wpdb ;
$metric = $metric === 'unique_visitors' ? 'unique_visitors' : 'views' ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, { $metric } AS metric_value FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( int ) $target [ 'post_id' ],
$start ,
$end
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, { $metric } AS metric_value FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( string ) $target [ 'page_key' ],
$start ,
$end
), ARRAY_A );
}
$indexed = [];
foreach (( array ) $rows as $row ) {
$indexed [( string ) $row [ 'stat_date' ]] = ( int ) $row [ 'metric_value' ];
}
$data = [];
$cursor = strtotime ( $start );
$end_ts = strtotime ( $end );
while ( $cursor <= $end_ts ) {
$date = gmdate ( 'Y-m-d' , $cursor );
$data [] = [
'date' => $date ,
'views' => $indexed [ $date ] ? ? 0 ,
];
$cursor = strtotime ( '+1 day' , $cursor );
}
return $data ;
}
protected function get_target_monthly_chart_data_between ( array $target , string $start , string $end ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT DATE_FORMAT(stat_date, '%%Y-%%m') AS month_key, SUM(views) AS total_views FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s GROUP BY month_key ORDER BY month_key ASC " ,
( int ) $target [ 'post_id' ],
$start ,
$end
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT DATE_FORMAT(stat_date, '%%Y-%%m') AS month_key, SUM(views) AS total_views FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s GROUP BY month_key ORDER BY month_key ASC " ,
( string ) $target [ 'page_key' ],
$start ,
$end
), ARRAY_A );
}
$indexed = [];
foreach (( array ) $rows as $row ) {
$indexed [( string ) $row [ 'month_key' ]] = ( int ) $row [ 'total_views' ];
}
$data = [];
$cursor = strtotime ( gmdate ( 'Y-m-01' , strtotime ( $start )));
$end_month = strtotime ( gmdate ( 'Y-m-01' , strtotime ( $end )));
while ( $cursor <= $end_month ) {
$month_key = gmdate ( 'Y-m' , $cursor );
$data [] = [
'label' => wp_date ( 'M Y' , $cursor ),
'views' => $indexed [ $month_key ] ? ? 0 ,
];
$cursor = strtotime ( '+1 month' , $cursor );
}
return $data ;
}
protected function get_target_weekday_breakdown_between ( array $target , string $start , string $end ) {
$daily_data = $this -> get_target_daily_chart_data_between ( $target , $start , $end , 'views' );
$weekday_rows = [
__ ( 'Mon' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Tue' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Wed' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Thu' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Fri' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Sat' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Sun' , 'ansico-stat-plugin' ) => 0 ,
];
$weekday_keys = array_keys ( $weekday_rows );
foreach ( $daily_data as $row ) {
$timestamp = strtotime (( string ) ( $row [ 'date' ] ? ? '' ));
if ( ! $timestamp ) {
continue ;
}
$weekday_index = ( int ) gmdate ( 'N' , $timestamp ) - 1 ;
if ( isset ( $weekday_keys [ $weekday_index ])) {
$weekday_rows [ $weekday_keys [ $weekday_index ]] += ( int ) ( $row [ 'views' ] ? ? 0 );
}
}
$data = [];
foreach ( $weekday_rows as $label => $views ) {
$data [] = [
'label' => $label ,
'views' => ( int ) $views ,
];
}
return $data ;
}
protected function render_dual_chart_markup ( array $views_data , array $unique_data , string $chart_id , string $aria_label = '' ) {
if ( $aria_label === '' ) {
$aria_label = __ ( 'Views and unique visitors chart' , 'ansico-stat-plugin' );
}
$count = max ( count ( $views_data ), count ( $unique_data ));
if ( $count < 1 ) {
return '<p>' . esc_html__ ( 'No chart data available.' , 'ansico-stat-plugin' ) . '</p>' ;
}
$values = [];
foreach ( $views_data as $row ) {
$values [] = ( int ) ( $row [ 'views' ] ? ? 0 );
}
foreach ( $unique_data as $row ) {
$values [] = ( int ) ( $row [ 'views' ] ? ? 0 );
}
$max = max ( 1 , ! empty ( $values ) ? max ( $values ) : 1 );
$width = 920 ;
$height = 240 ;
$chart_top = 20 ;
$chart_bottom = 185 ;
$chart_left = 26 ;
$chart_right = 830 ;
$stepX = $count > 1 ? ( $chart_right - $chart_left ) / ( $count - 1 ) : ( $chart_right - $chart_left );
$build_points = static function ( array $series , string $series_label ) use ( $chart_top , $chart_bottom , $chart_left , $stepX , $max ) {
$points = [];
foreach ( array_values ( $series ) as $index => $row ) {
$value = ( int ) ( $row [ 'views' ] ? ? 0 );
$x = $chart_left + ( $stepX * $index );
$y = $value > 0 ? $chart_top + (( $chart_bottom - $chart_top ) * ( 1 - ( $value / $max ))) : $chart_bottom ;
$label = ( string ) ( $row [ 'label' ] ? ? $row [ 'date' ] ? ? '' );
$points [] = [
'x' => $x ,
'y' => $y ,
'label' => $label ,
'value' => $value ,
'series_label' => $series_label ,
];
}
return $points ;
};
$view_points = $build_points ( $views_data , __ ( 'Views' , 'ansico-stat-plugin' ));
$unique_points = $build_points ( $unique_data , __ ( 'Unique visitors' , 'ansico-stat-plugin' ));
$view_path = implode ( ' ' , array_map ( static function ( $point ) {
return $point [ 'x' ] . ',' . $point [ 'y' ];
}, $view_points ));
$unique_path = implode ( ' ' , array_map ( static function ( $point ) {
return $point [ 'x' ] . ',' . $point [ 'y' ];
}, $unique_points ));
ob_start ();
?>
< div class = " ansico-stat-chart-wrap ansico-stat-chart-wrap-enhanced " id = " <?php echo esc_attr( $chart_id ); ?>-wrap " >
< div class = " ansico-stat-line-chart ansico-stat-line-chart-enhanced ansico-stat-line-chart-dual " id = " <?php echo esc_attr( $chart_id ); ?> " >
< svg viewBox = " 0 0 <?php echo esc_attr((string) $width ); ?> <?php echo esc_attr((string) $height ); ?> " width = " 100% " height = " <?php echo esc_attr((string) $height ); ?> " role = " img " aria - label = " <?php echo esc_attr( $aria_label ); ?> " >
< line x1 = " <?php echo esc_attr((string) $chart_left ); ?> " y1 = " <?php echo esc_attr((string) $chart_bottom ); ?> " x2 = " <?php echo esc_attr((string) $chart_right ); ?> " y2 = " <?php echo esc_attr((string) $chart_bottom ); ?> " stroke = " #cbd5e1 " stroke - width = " 1.2 " ></ line >
< ? php for ( $i = 0 ; $i <= 4 ; $i ++ ) : $ratio = $i / 4 ; $y = $chart_top + (( $chart_bottom - $chart_top ) * $ratio ); ?>
< line x1 = " <?php echo esc_attr((string) $chart_left ); ?> " y1 = " <?php echo esc_attr((string) $y ); ?> " x2 = " <?php echo esc_attr((string) $chart_right ); ?> " y2 = " <?php echo esc_attr((string) $y ); ?> " stroke = " #e5e7eb " stroke - width = " 1 " ></ line >
< ? php endfor ; ?>
< ? php if ( $view_path !== '' ) : ?>
< polyline fill = " none " points = " <?php echo esc_attr( $view_path ); ?> " stroke = " #2271b1 " stroke - width = " 3 " stroke - linecap = " round " stroke - linejoin = " round " ></ polyline >
< ? php endif ; ?>
< ? php if ( $unique_path !== '' ) : ?>
< polyline fill = " none " points = " <?php echo esc_attr( $unique_path ); ?> " stroke = " #8c8f94 " stroke - width = " 3 " stroke - dasharray = " 7 5 " stroke - linecap = " round " stroke - linejoin = " round " ></ polyline >
< ? php endif ; ?>
< ? php foreach ( $view_points as $point ) : ?>
< rect class = " ansico-stat-chart-point-hit " x = " <?php echo esc_attr((string) ( $point['x'] - 18)); ?> " y = " <?php echo esc_attr((string) ( $point['y'] - 18)); ?> " width = " 36 " height = " 36 " rx = " 18 " ry = " 18 " fill = " transparent " data - label = " <?php echo esc_attr( $point['series_label'] . ' — ' . $point['label'] ); ?> " data - value = " <?php echo esc_attr(number_format_i18n( $point['value'] )); ?> " ></ rect >
< circle class = " ansico-stat-chart-point ansico-stat-chart-point-views " cx = " <?php echo esc_attr((string) $point['x'] ); ?> " cy = " <?php echo esc_attr((string) $point['y'] ); ?> " r = " 4 " fill = " #ffffff " stroke = " #2271b1 " stroke - width = " 2.5 " ></ circle >
< ? php endforeach ; ?>
< ? php foreach ( $unique_points as $point ) : ?>
< rect class = " ansico-stat-chart-point-hit " x = " <?php echo esc_attr((string) ( $point['x'] - 18)); ?> " y = " <?php echo esc_attr((string) ( $point['y'] - 18)); ?> " width = " 36 " height = " 36 " rx = " 18 " ry = " 18 " fill = " transparent " data - label = " <?php echo esc_attr( $point['series_label'] . ' — ' . $point['label'] ); ?> " data - value = " <?php echo esc_attr(number_format_i18n( $point['value'] )); ?> " ></ rect >
< circle class = " ansico-stat-chart-point ansico-stat-chart-point-unique " cx = " <?php echo esc_attr((string) $point['x'] ); ?> " cy = " <?php echo esc_attr((string) $point['y'] ); ?> " r = " 4 " fill = " #ffffff " stroke = " #8c8f94 " stroke - width = " 2.5 " ></ circle >
< ? php endforeach ; ?>
</ svg >
</ div >
< div class = " ansico-stat-chart-tooltip " style = " display:none; left:-9999px; top:-9999px; opacity:0; " aria - hidden = " true " ></ div >
< div class = " ansico-stat-dual-chart-legend " >< span >< span class = " ansico-stat-dual-chart-swatch ansico-stat-dual-chart-swatch-views " ></ span >< ? php echo esc_html__ ( 'Views' , 'ansico-stat-plugin' ); ?> </span><span><span class="ansico-stat-dual-chart-swatch ansico-stat-dual-chart-swatch-unique"></span><?php echo esc_html__('Unique visitors', 'ansico-stat-plugin'); ?></span></div>
</ div >
< script >
( function () {
var root = document . getElementById ( < ? php echo wp_json_encode ( $chart_id ); ?> );
var wrap = document . getElementById ( < ? php echo wp_json_encode ( $chart_id . '-wrap' ); ?> );
if ( ! root || ! wrap ) return ;
var tooltip = wrap . querySelector ( '.ansico-stat-chart-tooltip' );
var points = root . querySelectorAll ( '.ansico-stat-chart-point-hit' );
if ( ! tooltip || ! points . length ) return ;
function resetTooltip () {
tooltip . style . display = 'none' ;
tooltip . style . opacity = '0' ;
tooltip . style . left = '-9999px' ;
tooltip . style . top = '-9999px' ;
}
function positionTooltipForPoint ( point ) {
var wrapRect = wrap . getBoundingClientRect ();
var pointRect = point . getBoundingClientRect ();
var tipRect = tooltip . getBoundingClientRect ();
var pointCenterX = ( pointRect . left - wrapRect . left ) + ( pointRect . width / 2 );
var pointTopY = ( pointRect . top - wrapRect . top );
var left = pointCenterX - ( tipRect . width / 2 );
var top = pointTopY - tipRect . height - 10 ;
if ( left + tipRect . width > wrapRect . width - 8 ) left = wrapRect . width - tipRect . width - 8 ;
if ( left < 8 ) left = 8 ;
if ( top < 8 ) top = pointTopY + 18 ;
tooltip . style . left = left + 'px' ;
tooltip . style . top = top + 'px' ;
}
function showTooltip ( event ) {
var point = event . currentTarget ;
tooltip . textContent = point . getAttribute ( 'data-label' ) + ': ' + point . getAttribute ( 'data-value' );
tooltip . style . display = 'block' ;
tooltip . style . opacity = '1' ;
positionTooltipForPoint ( point );
}
points . forEach ( function ( point ) {
point . addEventListener ( 'mouseenter' , showTooltip );
point . addEventListener ( 'mouseleave' , resetTooltip );
});
wrap . addEventListener ( 'mouseleave' , resetTooltip );
resetTooltip ();
})();
</ script >
< ? php
return ( string ) ob_get_clean ();
}
protected function get_target_unique_daily_chart_data ( array $target , int $days = 60 ) {
global $wpdb ;
$days = max ( 1 , $days );
$start = $this -> date_days_ago ( $days - 1 );
$end = current_time ( 'Y-m-d' );
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, unique_visitors FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( int ) $target [ 'post_id' ],
$start ,
$end
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, unique_visitors FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( string ) $target [ 'page_key' ],
$start ,
$end
), ARRAY_A );
}
$indexed = [];
foreach (( array ) $rows as $row ) {
$indexed [( string ) $row [ 'stat_date' ]] = ( int ) $row [ 'unique_visitors' ];
}
$data = [];
$cursor = strtotime ( $start );
$end_ts = strtotime ( $end );
while ( $cursor <= $end_ts ) {
$date = gmdate ( 'Y-m-d' , $cursor );
$data [] = [
'date' => $date ,
'views' => $indexed [ $date ] ? ? 0 ,
];
$cursor = strtotime ( '+1 day' , $cursor );
}
return $data ;
}
protected function get_target_period_views ( array $target , string $start , string $end ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$views = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(views) FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s " ,
( int ) $target [ 'post_id' ],
$start ,
$end
));
return ( int ) $views ;
}
$table = self :: page_daily_table_name ();
$views = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT SUM(views) FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s " ,
( string ) $target [ 'page_key' ],
$start ,
$end
));
return ( int ) $views ;
}
protected function get_target_daily_chart_data ( array $target , int $days = 60 ) {
global $wpdb ;
$days = max ( 1 , $days );
$start = $this -> date_days_ago ( $days - 1 );
$end = current_time ( 'Y-m-d' );
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, views FROM { $table } WHERE post_id = %d AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( int ) $target [ 'post_id' ],
$start ,
$end
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT stat_date, views FROM { $table } WHERE page_key = %s AND stat_date BETWEEN %s AND %s ORDER BY stat_date ASC " ,
( string ) $target [ 'page_key' ],
$start ,
$end
), ARRAY_A );
}
$indexed = [];
foreach (( array ) $rows as $row ) {
$indexed [( string ) $row [ 'stat_date' ]] = ( int ) $row [ 'views' ];
}
$data = [];
$cursor = strtotime ( $start );
$end_ts = strtotime ( $end );
while ( $cursor <= $end_ts ) {
$date = gmdate ( 'Y-m-d' , $cursor );
$data [] = [
'date' => $date ,
'views' => $indexed [ $date ] ? ? 0 ,
];
$cursor = strtotime ( '+1 day' , $cursor );
}
return $data ;
}
protected function get_target_monthly_chart_data ( array $target , int $months = 12 ) {
global $wpdb ;
$months = max ( 1 , $months );
$current_month = strtotime ( wp_date ( 'Y-m-01' , current_time ( 'timestamp' )));
$start_month = strtotime ( '-' . ( $months - 1 ) . ' months' , $current_month );
$start = wp_date ( 'Y-m-01' , $start_month );
$end = wp_date ( 'Y-m-t' , current_time ( 'timestamp' ));
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT DATE_FORMAT(stat_date, '%%Y-%%m') AS month_key, SUM(views) AS total_views \n FROM { $table } \n WHERE post_id = %d AND stat_date BETWEEN %s AND %s \n GROUP BY month_key \n ORDER BY month_key ASC " ,
( int ) $target [ 'post_id' ],
$start ,
$end
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$rows = $wpdb -> get_results ( $wpdb -> prepare (
" SELECT DATE_FORMAT(stat_date, '%%Y-%%m') AS month_key, SUM(views) AS total_views \n FROM { $table } \n WHERE page_key = %s AND stat_date BETWEEN %s AND %s \n GROUP BY month_key \n ORDER BY month_key ASC " ,
( string ) $target [ 'page_key' ],
$start ,
$end
), ARRAY_A );
}
$indexed = [];
foreach (( array ) $rows as $row ) {
$indexed [( string ) $row [ 'month_key' ]] = ( int ) $row [ 'total_views' ];
}
$data = [];
$cursor = $start_month ;
while ( $cursor <= $current_month ) {
$month_key = wp_date ( 'Y-m' , $cursor );
$data [] = [
'label' => wp_date ( 'M Y' , $cursor ),
'views' => $indexed [ $month_key ] ? ? 0 ,
];
$cursor = strtotime ( '+1 month' , $cursor );
}
return $data ;
}
protected function get_target_best_day ( array $target ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$row = $wpdb -> get_row ( $wpdb -> prepare (
" SELECT stat_date, views FROM { $table } WHERE post_id = %d ORDER BY views DESC, stat_date ASC LIMIT 1 " ,
( int ) $target [ 'post_id' ]
), ARRAY_A );
} else {
$table = self :: page_daily_table_name ();
$row = $wpdb -> get_row ( $wpdb -> prepare (
" SELECT stat_date, views FROM { $table } WHERE page_key = %s ORDER BY views DESC, stat_date ASC LIMIT 1 " ,
( string ) $target [ 'page_key' ]
), ARRAY_A );
}
return is_array ( $row ) ? $row : [];
}
protected function get_target_first_tracked_date ( array $target ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$date = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT MIN(stat_date) FROM { $table } WHERE post_id = %d " ,
( int ) $target [ 'post_id' ]
));
} else {
$table = self :: page_daily_table_name ();
$date = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT MIN(stat_date) FROM { $table } WHERE page_key = %s " ,
( string ) $target [ 'page_key' ]
));
}
return is_string ( $date ) ? $date : '' ;
}
protected function get_target_total_tracked_days ( array $target ) {
global $wpdb ;
if (( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$table = self :: post_daily_table_name ();
$count = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT COUNT(*) FROM { $table } WHERE post_id = %d " ,
( int ) $target [ 'post_id' ]
));
} else {
$table = self :: page_daily_table_name ();
$count = $wpdb -> get_var ( $wpdb -> prepare (
" SELECT COUNT(*) FROM { $table } WHERE page_key = %s " ,
( string ) $target [ 'page_key' ]
));
}
return ( int ) $count ;
}
protected function get_target_average_unique_visitors_per_day ( array $target , int $days ) {
$days = max ( 1 , $days );
$uniques = $this -> get_target_period_unique_visitors ( $target , $this -> date_days_ago ( $days - 1 ), current_time ( 'Y-m-d' ));
return $uniques / $days ;
}
protected function get_target_unique_period_comparison ( array $target , int $days ) {
$days = max ( 1 , $days );
$current_end = current_time ( 'Y-m-d' );
$current_start = $this -> date_days_ago ( $days - 1 );
$previous_end = $this -> date_days_ago ( $days );
$previous_start = $this -> date_days_ago (( $days * 2 ) - 1 );
$current_uniques = $this -> get_target_period_unique_visitors ( $target , $current_start , $current_end );
$previous_uniques = $this -> get_target_period_unique_visitors ( $target , $previous_start , $previous_end );
$change = $current_uniques - $previous_uniques ;
$percent_change = null ;
if ( $previous_uniques > 0 ) {
$percent_change = (( $current_uniques - $previous_uniques ) / $previous_uniques ) * 100 ;
} elseif ( $current_uniques > 0 ) {
$percent_change = 100.0 ;
}
return [
'days' => $days ,
'current_views' => ( int ) $current_uniques ,
'previous_views' => ( int ) $previous_uniques ,
'change' => ( int ) $change ,
'percent_change' => $percent_change ,
'current_start' => $current_start ,
'current_end' => $current_end ,
'previous_start' => $previous_start ,
'previous_end' => $previous_end ,
];
}
protected function get_target_average_views_per_day ( array $target , int $days ) {
$days = max ( 1 , $days );
$views = $this -> get_target_period_views ( $target , $this -> date_days_ago ( $days - 1 ), current_time ( 'Y-m-d' ));
return $views / $days ;
}
protected function get_target_period_comparison ( array $target , int $days ) {
$days = max ( 1 , $days );
$current_end = current_time ( 'Y-m-d' );
$current_start = $this -> date_days_ago ( $days - 1 );
$previous_end = $this -> date_days_ago ( $days );
$previous_start = $this -> date_days_ago (( $days * 2 ) - 1 );
$current_views = $this -> get_target_period_views ( $target , $current_start , $current_end );
$previous_views = $this -> get_target_period_views ( $target , $previous_start , $previous_end );
$change = $current_views - $previous_views ;
$percent_change = null ;
if ( $previous_views > 0 ) {
$percent_change = (( $current_views - $previous_views ) / $previous_views ) * 100 ;
} elseif ( $current_views > 0 ) {
$percent_change = 100.0 ;
}
return [
'days' => $days ,
'current_views' => ( int ) $current_views ,
'previous_views' => ( int ) $previous_views ,
'change' => ( int ) $change ,
'percent_change' => $percent_change ,
'current_start' => $current_start ,
'current_end' => $current_end ,
'previous_start' => $previous_start ,
'previous_end' => $previous_end ,
];
}
protected function format_change_percent ( $percent_change ) {
if ( $percent_change === null ) {
return __ ( 'New data' , 'ansico-stat-plugin' );
}
$prefix = $percent_change > 0 ? '+' : '' ;
return $prefix . number_format_i18n (( float ) $percent_change , 1 ) . '%' ;
}
protected function get_target_weekday_breakdown ( array $target , int $days = 90 ) {
$days = max ( 1 , $days );
$daily_data = $this -> get_target_daily_chart_data ( $target , $days );
$weekday_rows = [
__ ( 'Mon' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Tue' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Wed' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Thu' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Fri' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Sat' , 'ansico-stat-plugin' ) => 0 ,
__ ( 'Sun' , 'ansico-stat-plugin' ) => 0 ,
];
$weekday_keys = array_keys ( $weekday_rows );
foreach ( $daily_data as $row ) {
$timestamp = strtotime (( string ) ( $row [ 'date' ] ? ? '' ));
if ( ! $timestamp ) {
continue ;
}
$weekday_index = ( int ) gmdate ( 'N' , $timestamp ) - 1 ;
if ( ! isset ( $weekday_keys [ $weekday_index ])) {
continue ;
}
$weekday_rows [ $weekday_keys [ $weekday_index ]] += ( int ) ( $row [ 'views' ] ? ? 0 );
}
$data = [];
foreach ( $weekday_rows as $label => $views ) {
$data [] = [
'label' => $label ,
'views' => ( int ) $views ,
];
}
return $data ;
}
protected function build_single_export_url ( string $target_url , string $start_date = '' , string $end_date = '' ) {
$args = [
'action' => 'ansico_stat_export_single_csv' ,
'target_url' => $target_url ,
];
if ( $start_date !== '' ) {
$args [ 'start_date' ] = $start_date ;
}
if ( $end_date !== '' ) {
$args [ 'end_date' ] = $end_date ;
}
return wp_nonce_url (
add_query_arg ( $args , admin_url ( 'admin-post.php' )),
'ansico_stat_export_single_csv'
);
}
protected function render_single_stats_summary_cards ( array $items ) {
echo '<div class="ansico-stat-summary-grid">' ;
foreach ( $items as $item ) {
echo '<div class="ansico-stat-summary-card">' ;
echo '<div class="ansico-stat-summary-label">' . esc_html (( string ) ( $item [ 'label' ] ? ? '' )) . '</div>' ;
echo '<div class="ansico-stat-summary-value">' . esc_html (( string ) ( $item [ 'value' ] ? ? '' )) . '</div>' ;
if ( ! empty ( $item [ 'description' ])) {
echo '<div class="ansico-stat-summary-description">' . esc_html (( string ) $item [ 'description' ]) . '</div>' ;
}
echo '</div>' ;
}
echo '</div>' ;
}
public function render_single_page_stats_page () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to access this page.' , 'ansico-stat-plugin' ));
}
$requested_url = $this -> get_requested_single_stats_url ();
$date_range = $this -> get_single_stats_date_range ();
$range_start = ( string ) $date_range [ 'start' ];
$range_end = ( string ) $date_range [ 'end' ];
$range_days = ( int ) $date_range [ 'days' ];
$result = $this -> resolve_single_stats_target ( $requested_url );
$target = ! empty ( $result [ 'success' ]) ? ( array ) $result [ 'target' ] : [];
$daily_chart = ! empty ( $target ) ? $this -> get_target_daily_chart_data_between ( $target , $range_start , $range_end , 'views' ) : [];
$unique_daily_chart = ! empty ( $target ) ? $this -> get_target_daily_chart_data_between ( $target , $range_start , $range_end , 'unique_visitors' ) : [];
$monthly_chart = ! empty ( $target ) ? $this -> get_target_monthly_chart_data_between ( $target , $range_start , $range_end ) : [];
$weekday_chart = ! empty ( $target ) ? $this -> get_target_weekday_breakdown_between ( $target , $range_start , $range_end ) : [];
$lifetime_views = ! empty ( $target ) ? $this -> get_target_lifetime_views ( $target ) : 0 ;
$lifetime_unique_visitors = ! empty ( $target ) ? $this -> get_target_lifetime_unique_visitors ( $target ) : 0 ;
$views_today = ! empty ( $target ) ? $this -> get_target_period_views ( $target , current_time ( 'Y-m-d' ), current_time ( 'Y-m-d' )) : 0 ;
$unique_today = ! empty ( $target ) ? $this -> get_target_period_unique_visitors ( $target , current_time ( 'Y-m-d' ), current_time ( 'Y-m-d' )) : 0 ;
$range_views = ! empty ( $target ) ? $this -> get_target_period_views ( $target , $range_start , $range_end ) : 0 ;
$range_unique = ! empty ( $target ) ? $this -> get_target_period_unique_visitors ( $target , $range_start , $range_end ) : 0 ;
$best_day = ! empty ( $target ) ? $this -> get_target_best_day ( $target ) : [];
$first_tracked = ! empty ( $target ) ? $this -> get_target_first_tracked_date ( $target ) : '' ;
$tracked_days = ! empty ( $target ) ? $this -> get_target_total_tracked_days ( $target ) : 0 ;
$avg_range = $range_days > 0 ? ( $range_views / $range_days ) : 0 ;
$avg_unique_range = $range_days > 0 ? ( $range_unique / $range_days ) : 0 ;
$compare_range = ! empty ( $target ) ? $this -> get_target_period_comparison ( $target , $range_days ) : [];
$unique_compare_range = ! empty ( $target ) ? $this -> get_target_unique_period_comparison ( $target , $range_days ) : [];
$views_7 = ! empty ( $target ) ? $this -> get_target_period_views ( $target , $this -> date_days_ago ( 6 ), current_time ( 'Y-m-d' )) : 0 ;
$views_30 = ! empty ( $target ) ? $this -> get_target_period_views ( $target , $this -> date_days_ago ( 29 ), current_time ( 'Y-m-d' )) : 0 ;
$unique_7 = ! empty ( $target ) ? $this -> get_target_period_unique_visitors ( $target , $this -> date_days_ago ( 6 ), current_time ( 'Y-m-d' )) : 0 ;
$unique_30 = ! empty ( $target ) ? $this -> get_target_period_unique_visitors ( $target , $this -> date_days_ago ( 29 ), current_time ( 'Y-m-d' )) : 0 ;
$export_url = ! empty ( $target ) ? $this -> build_single_export_url ( $requested_url , $range_start , $range_end ) : '' ;
$reset_range_url = $this -> build_single_stats_admin_url ( $requested_url );
$post_type_label = '' ;
if ( ! empty ( $target ) && ( $target [ 'kind' ] ? ? '' ) === 'singular' ) {
$post_type_obj = get_post_type_object (( string ) ( $target [ 'post_type' ] ? ? '' ));
$post_type_label = $post_type_obj && ! empty ( $post_type_obj -> labels -> singular_name ) ? ( string ) $post_type_obj -> labels -> singular_name : ( string ) ( $target [ 'post_type' ] ? ? '' );
}
?>
< div class = " wrap ansico-stat-admin-page " >
< h1 >< ? php echo esc_html__ ( 'Ansico Stat Plugin — Single page statistics' , 'ansico-stat-plugin' ); ?> </h1>
< p >< ? php echo esc_html__ ( 'Enter a URL from this WordPress site to see the statistics already collected for that specific page, post, or tracked custom post type entry.' , 'ansico-stat-plugin' ); ?> </p>
< div class = " ansico-stat-card " >
< form method = " get " class = " ansico-stat-url-form " >
< input type = " hidden " name = " page " value = " <?php echo esc_attr(self::MENU_SLUG_SINGLE); ?> " />
< div class = " ansico-stat-url-form-row " >
< div class = " ansico-stat-url-field " >
< label for = " ansico-stat-target-url " >< strong >< ? php echo esc_html__ ( 'URL' , 'ansico-stat-plugin' ); ?> </strong></label>
< input type = " url " class = " regular-text code " style = " width:100%;max-width:none; " id = " ansico-stat-target-url " name = " target_url " value = " <?php echo esc_attr( $requested_url ); ?> " placeholder = " <?php echo esc_attr(home_url('/')); ?> " />
</ div >
< div class = " ansico-stat-date-field " >
< label for = " ansico-stat-start-date " >< strong >< ? php echo esc_html__ ( 'From' , 'ansico-stat-plugin' ); ?> </strong></label>
< input type = " date " id = " ansico-stat-start-date " name = " start_date " value = " <?php echo esc_attr( $range_start ); ?> " />
</ div >
< div class = " ansico-stat-date-field " >
< label for = " ansico-stat-end-date " >< strong >< ? php echo esc_html__ ( 'To' , 'ansico-stat-plugin' ); ?> </strong></label>
< input type = " date " id = " ansico-stat-end-date " name = " end_date " value = " <?php echo esc_attr( $range_end ); ?> " />
</ div >
< div class = " ansico-stat-url-action " >
< ? php submit_button ( __ ( 'Update' , 'ansico-stat-plugin' ), 'secondary' , '' , false ); ?>
</ div >
</ div >
< p class = " description " style = " margin:10px 0 0 0; " >< ? php echo esc_html__ ( 'Leave the date fields as-is for the default 30-day report, or choose a custom range for the selected page.' , 'ansico-stat-plugin' ); ?> <?php if (!empty($date_range['is_custom'])) : ?><a href="<?php echo esc_url($reset_range_url); ?>"><?php echo esc_html__('Reset range', 'ansico-stat-plugin'); ?></a><?php endif; ?></p>
</ form >
</ div >
< ? php if ( empty ( $result [ 'success' ])) : ?>
< div class = " ansico-stat-card " >
< p >< ? php echo esc_html (( string ) ( $result [ 'message' ] ? ? __ ( 'No page selected.' , 'ansico-stat-plugin' ))); ?> </p>
</ div >
< ? php else : ?>
< div class = " ansico-stat-card " >
< div class = " ansico-stat-card-header " >
< div >
< h2 style = " margin-bottom:6px; " >< ? php echo esc_html (( string ) ( $target [ 'label' ] ? ? '' )); ?> </h2>
< p class = " description " style = " margin:0; " >< ? php echo esc_html (( string ) ( $target [ 'url' ] ? ? '' )); ?> </p>
< p class = " description " style = " margin:4px 0 0 0; " >< ? php echo esc_html ( sprintf ( __ ( 'Selected period: %1$s to %2$s (%3$d days)' , 'ansico-stat-plugin' ), wp_date ( get_option ( 'date_format' ), strtotime ( $range_start )), wp_date ( get_option ( 'date_format' ), strtotime ( $range_end )), $range_days )); ?> </p>
</ div >
< div class = " ansico-stat-target-meta " style = " display:flex;gap:8px;align-items:center;flex-wrap:wrap; " >
< span class = " ansico-stat-target-badge " >< ? php echo esc_html (( $target [ 'kind' ] ? ? '' ) === 'singular' ? ( $post_type_label ? : __ ( 'Post' , 'ansico-stat-plugin' )) : ( string ) ( $target [ 'page_type' ] ? ? __ ( 'Page' , 'ansico-stat-plugin' ))); ?> </span>
< a class = " button button-secondary " href = " <?php echo esc_url( $export_url ); ?> " >< ? php echo esc_html__ ( 'Export page CSV' , 'ansico-stat-plugin' ); ?> </a>
</ div >
</ div >
< ? php $this -> render_single_stats_summary_cards ([
[
'label' => __ ( 'Views in selected period' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $range_views ),
'description' => sprintf ( __ ( 'Avg. %s per day' , 'ansico-stat-plugin' ), number_format_i18n ( $avg_range , 1 )),
],
[
'label' => __ ( 'Unique visitors in selected period' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $range_unique ),
'description' => sprintf ( __ ( 'Avg. %s per day' , 'ansico-stat-plugin' ), number_format_i18n ( $avg_unique_range , 1 )),
],
[
'label' => __ ( 'Views today' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $views_today ),
'description' => __ ( 'Today only' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Unique visitors today' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $unique_today ),
'description' => __ ( 'Today only' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Views last 7 days' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $views_7 ),
'description' => __ ( 'Rolling 7-day total' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Unique visitors last 7 days' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $unique_7 ),
'description' => __ ( 'Rolling 7-day total' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Views last 30 days' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $views_30 ),
'description' => __ ( 'Rolling 30-day total' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Unique visitors last 30 days' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $unique_30 ),
'description' => __ ( 'Rolling 30-day total' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Lifetime views' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $lifetime_views ),
'description' => $first_tracked !== '' ? sprintf ( __ ( 'Tracked since %s' , 'ansico-stat-plugin' ), wp_date ( get_option ( 'date_format' ), strtotime ( $first_tracked ))) : '' ,
],
[
'label' => __ ( 'Lifetime unique visitors' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $lifetime_unique_visitors ),
'description' => __ ( 'Stored from version 1.0.0.6 and forward' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Best day' , 'ansico-stat-plugin' ),
'value' => ! empty ( $best_day [ 'views' ]) ? number_format_i18n (( int ) $best_day [ 'views' ]) : '0' ,
'description' => ! empty ( $best_day [ 'stat_date' ]) ? wp_date ( get_option ( 'date_format' ), strtotime (( string ) $best_day [ 'stat_date' ])) : __ ( 'No tracked data yet' , 'ansico-stat-plugin' ),
],
[
'label' => __ ( 'Tracked days with views' , 'ansico-stat-plugin' ),
'value' => number_format_i18n ( $tracked_days ),
'description' => __ ( 'Days stored in the daily statistics table' , 'ansico-stat-plugin' ),
],
]); ?>
< p class = " description " style = " margin-top:12px; " >< ? php echo esc_html__ ( 'Unique visitors are stored per page/per day from version 1.0.0.6 and forward. Older historical rows may still show 0 unique visitors because that data was not collected before this build.' , 'ansico-stat-plugin' ); ?> </p>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Views and unique visitors' , 'ansico-stat-plugin' ); ?> </h2>
< ? php echo $this -> render_dual_chart_markup ( $daily_chart , $unique_daily_chart , 'ansico-single-target-dual-chart' , __ ( 'Views and unique visitors for selected page' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Daily views (%d days)' , 'ansico-stat-plugin' ), $range_days )); ?> </h2>
< ? php echo $this -> render_chart_markup ( $daily_chart , 'ansico-single-target-daily-chart' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Daily unique visitors (%d days)' , 'ansico-stat-plugin' ), $range_days )); ?> </h2>
< ? php echo $this -> render_chart_markup ( $unique_daily_chart , 'ansico-single-target-unique-daily-chart' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Monthly totals for selected period' , 'ansico-stat-plugin' ); ?> </h2>
< ? php echo $this -> render_bar_chart_markup ( $monthly_chart , 'ansico-single-target-monthly-chart' , __ ( 'Monthly totals for selected page' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Weekday distribution for selected period' , 'ansico-stat-plugin' ); ?> </h2>
< ? php echo $this -> render_bar_chart_markup ( $weekday_chart , 'ansico-single-target-weekday-chart' , __ ( 'Views by weekday for selected page' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Selected period comparison' , 'ansico-stat-plugin' ); ?> </h2>
< table class = " widefat striped ansico-stat-table " >
< thead >
< tr >
< th >< ? php echo esc_html__ ( 'Metric' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Current period' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Previous period' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Change' , 'ansico-stat-plugin' ); ?> </th>
< th >< ? php echo esc_html__ ( 'Change %' , 'ansico-stat-plugin' ); ?> </th>
</ tr >
</ thead >
< tbody >
< tr >
< td >< ? php echo esc_html__ ( 'Views' , 'ansico-stat-plugin' ); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $compare_range [ 'current_views' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $compare_range [ 'previous_views' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $compare_range [ 'change' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( $this -> format_change_percent ( $compare_range [ 'percent_change' ] ? ? null )); ?> </td>
</ tr >
< tr >
< td >< ? php echo esc_html__ ( 'Unique visitors' , 'ansico-stat-plugin' ); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $unique_compare_range [ 'current_views' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $unique_compare_range [ 'previous_views' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( number_format_i18n (( int ) ( $unique_compare_range [ 'change' ] ? ? 0 ))); ?> </td>
< td >< ? php echo esc_html ( $this -> format_change_percent ( $unique_compare_range [ 'percent_change' ] ? ? null )); ?> </td>
</ tr >
</ tbody >
</ table >
</ div >
< ? php endif ; ?>
</ div >
< ? php
}
2026-04-15 19:53:03 +00:00
public function render_settings_page () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to access this page.' , 'ansico-stat-plugin' ));
}
$settings = $this -> get_settings ();
?>
< div class = " wrap ansico-stat-admin-page " >
< h1 >< ? php echo esc_html__ ( 'Ansico Stat Plugin' , 'ansico-stat-plugin' ); ?> </h1>
< p >< ? php echo esc_html__ ( 'Use this page to manage plugin settings and reset all collected view statistics.' , 'ansico-stat-plugin' ); ?> </p>
< p class = " description " >< ? php echo esc_html ( sprintf ( __ ( 'A single browser only counts one view per page within %d minutes. You can change this below.' , 'ansico-stat-plugin' ), ( int ) $settings [ 'revisit_minutes' ])); ?> </p>
< ? php if ( isset ( $_GET [ 'settings-updated' ]) && $_GET [ 'settings-updated' ] === 'true' ) : ?>
< div class = " notice notice-success is-dismissible " >< p >< ? php echo esc_html__ ( 'Settings saved.' , 'ansico-stat-plugin' ); ?> </p></div>
< ? php endif ; ?>
< ? php if ( isset ( $_GET [ 'reset' ]) && $_GET [ 'reset' ] === 'success' ) : ?>
< div class = " notice notice-success is-dismissible " >< p >< ? php echo esc_html__ ( 'All tracked view data has been reset.' , 'ansico-stat-plugin' ); ?> </p></div>
< ? php endif ; ?>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Tracking settings' , 'ansico-stat-plugin' ); ?> </h2>
< form method = " post " action = " <?php echo esc_url(admin_url('admin-post.php')); ?> " >
< ? php wp_nonce_field ( 'ansico_stat_save_settings' ); ?>
< input type = " hidden " name = " action " value = " ansico_stat_save_settings " />
< table class = " form-table " role = " presentation " >
< tbody >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Who should be counted' , 'ansico-stat-plugin' ); ?> </th>
< td >
< fieldset >
< label >< input type = " radio " name = " visitor_counting_mode " value = " count_all " < ? php checked ( $settings [ 'visitor_counting_mode' ], 'count_all' ); ?> /> <?php echo esc_html__('Count everyone, including logged in users and administrators', 'ansico-stat-plugin'); ?></label><br />
< label >< input type = " radio " name = " visitor_counting_mode " value = " exclude_admins " < ? php checked ( $settings [ 'visitor_counting_mode' ], 'exclude_admins' ); ?> /> <?php echo esc_html__('Count logged in users, but exclude administrators', 'ansico-stat-plugin'); ?></label><br />
< label >< input type = " radio " name = " visitor_counting_mode " value = " exclude_all_logged_in " < ? php checked ( $settings [ 'visitor_counting_mode' ], 'exclude_all_logged_in' ); ?> /> <?php echo esc_html__('Exclude all logged in users', 'ansico-stat-plugin'); ?></label>
</ fieldset >
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Count the same visitor again after' , 'ansico-stat-plugin' ); ?> </th>
< td >
< input type = " number " name = " revisit_minutes " min = " 1 " max = " 10080 " value = " <?php echo esc_attr((int) $settings['revisit_minutes'] ); ?> " class = " small-text " />
< span >< ? php echo esc_html__ ( 'minutes' , 'ansico-stat-plugin' ); ?> </span>
< p class = " description " >< ? php echo esc_html__ ( 'This controls how long the plugin waits before the same browser can count as another view for the same page again.' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Exclude known bots and crawlers' , 'ansico-stat-plugin' ); ?> </th>
< td >
< label >
< input type = " checkbox " name = " exclude_known_bots " value = " 1 " < ? php checked ( ! empty ( $settings [ 'exclude_known_bots' ])); ?> />
< ? php echo esc_html__ ( 'Skip tracking for requests that look like bots, crawlers, monitoring tools, or previews based on the user agent and request headers.' , 'ansico-stat-plugin' ); ?>
</ label >
< p class = " description " >< ? php echo esc_html__ ( 'Bot detection is heuristic. It blocks many common bots, but no detection method is perfect.' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Footer label' , 'ansico-stat-plugin' ); ?> </th>
< td >
< input type = " text " name = " frontend_label " value = " <?php echo esc_attr((string) $settings['frontend_label'] ); ?> " class = " regular-text " />
< p class = " description " >< ? php echo esc_html__ ( 'This text is shown before the count in the admin-only footer display. Default: Views' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Track singular content types' , 'ansico-stat-plugin' ); ?> </th>
< td >
< ? php
$public_post_types = get_post_types ([ 'public' => true ], 'objects' );
$excluded_types = [ 'attachment' , 'revision' , 'nav_menu_item' , 'custom_css' , 'customize_changeset' , 'oembed_cache' , 'user_request' , 'wp_block' , 'wp_template' , 'wp_template_part' , 'wp_navigation' , 'wp_global_styles' ];
foreach ( $public_post_types as $post_type => $post_type_object ) :
if ( in_array ( $post_type , $excluded_types , true )) {
continue ;
}
$label = $post_type_object -> labels -> singular_name ? ? $post_type ;
?>
< label style = " display:block; margin-bottom:6px; " >
< input type = " checkbox " name = " track_post_types[] " value = " <?php echo esc_attr( $post_type ); ?> " < ? php checked ( in_array ( $post_type , $settings [ 'track_post_types' ], true )); ?> />
< ? php echo esc_html ( $label . ' (' . $post_type . ')' ); ?>
</ label >
< ? php endforeach ; ?>
< p class = " description " >< ? php echo esc_html__ ( 'Choose which posts, pages, and public custom post types should be tracked.' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Track other page types' , 'ansico-stat-plugin' ); ?> </th>
< td >
< label style = " display:block; margin-bottom:6px; " >< input type = " checkbox " name = " track_front_page " value = " 1 " < ? php checked ( ! empty ( $settings [ 'track_front_page' ])); ?> /> <?php echo esc_html__('Front page', 'ansico-stat-plugin'); ?></label>
< label style = " display:block; margin-bottom:6px; " >< input type = " checkbox " name = " track_posts_page " value = " 1 " < ? php checked ( ! empty ( $settings [ 'track_posts_page' ])); ?> /> <?php echo esc_html__('Posts page / blog home', 'ansico-stat-plugin'); ?></label>
< label style = " display:block; margin-bottom:6px; " >< input type = " checkbox " name = " track_archives " value = " 1 " < ? php checked ( ! empty ( $settings [ 'track_archives' ])); ?> /> <?php echo esc_html__('Archive pages, including categories, tags, taxonomies, authors, date archives, and post type archives', 'ansico-stat-plugin'); ?></label>
< label style = " display:block; margin-bottom:6px; " >< input type = " checkbox " name = " track_search " value = " 1 " < ? php checked ( ! empty ( $settings [ 'track_search' ])); ?> /> <?php echo esc_html__('Search results', 'ansico-stat-plugin'); ?></label>
< label style = " display:block; margin-bottom:6px; " >< input type = " checkbox " name = " track_404 " value = " 1 " < ? php checked ( ! empty ( $settings [ 'track_404' ])); ?> /> <?php echo esc_html__('404 pages', 'ansico-stat-plugin'); ?></label>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Rows in top lists' , 'ansico-stat-plugin' ); ?> </th>
< td >
< input type = " number " name = " top_list_rows " min = " 1 " max = " 100 " value = " <?php echo esc_attr((int) $settings['top_list_rows'] ); ?> " class = " small-text " />
< p class = " description " >< ? php echo esc_html__ ( 'Controls how many rows are shown in top lists such as monthly, yearly, dashboard, and 404 lists.' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php echo esc_html__ ( 'Rows in referral tables' , 'ansico-stat-plugin' ); ?> </th>
< td >
< input type = " number " name = " referral_rows " min = " 1 " max = " 200 " value = " <?php echo esc_attr((int) $settings['referral_rows'] ); ?> " class = " small-text " />
< p class = " description " >< ? php echo esc_html__ ( 'Controls how many referral URLs are shown in each referral table.' , 'ansico-stat-plugin' ); ?> </p>
</ td >
</ tr >
</ tbody >
</ table >
< ? php submit_button ( __ ( 'Save settings' , 'ansico-stat-plugin' )); ?>
</ form >
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Reset all post views' , 'ansico-stat-plugin' ); ?> </h2>
< p >< ? php echo esc_html__ ( 'This deletes all lifetime counters and all daily, monthly, yearly, and referral statistics for posts, pages, and public custom post types. This action cannot be undone.' , 'ansico-stat-plugin' ); ?> </p>
< form method = " post " action = " <?php echo esc_url(admin_url('admin-post.php')); ?> " onsubmit = " return confirm('<?php echo esc_js(__('Are you sure you want to reset all Ansico Stat data?', 'ansico-stat-plugin')); ?>'); " >
< ? php wp_nonce_field ( 'ansico_stat_reset_all' ); ?>
< input type = " hidden " name = " action " value = " ansico_stat_reset_all " />
< ? php submit_button ( __ ( 'Reset all post views and statistics' , 'ansico-stat-plugin' ), 'delete' ); ?>
</ form >
</ div >
</ div >
< ? php
}
public function render_admin_page () {
$this -> render_monthly_stats_page ();
}
public function render_monthly_stats_page () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to access this page.' , 'ansico-stat-plugin' ));
}
$settings = $this -> get_settings ();
$top_rows = max ( 1 , min ( 100 , ( int ) $settings [ 'top_list_rows' ]));
$referral_rows_limit = max ( 1 , min ( 200 , ( int ) $settings [ 'referral_rows' ]));
$chart_data = $this -> get_daily_chart_data ( 60 );
$selected_month = $this -> get_selected_month ();
$monthly_rows = $this -> get_monthly_top_posts ( $selected_month , $top_rows );
$referral_summary = $this -> get_referral_summary_for_month ( $selected_month );
$pie_summary = [
__ ( 'Direct' , 'ansico-stat-plugin' ) => ( int ) $referral_summary [ 'direct' ],
__ ( 'Search engines' , 'ansico-stat-plugin' ) => ( int ) $referral_summary [ 'search' ],
__ ( 'Social media' , 'ansico-stat-plugin' ) => ( int ) $referral_summary [ 'social' ],
__ ( 'Other websites' , 'ansico-stat-plugin' ) => ( int ) $referral_summary [ 'website' ],
];
$search_referrals = $this -> get_referral_sources_for_month ( $selected_month , 'search' , $referral_rows_limit );
$social_referrals = $this -> get_referral_sources_for_month ( $selected_month , 'social' , $referral_rows_limit );
$website_referrals = $this -> get_referral_sources_for_month ( $selected_month , 'website' , $referral_rows_limit );
$monthly_404_rows = $this -> get_top_404_pages_for_month ( $selected_month , $top_rows );
$monthly_country_rows = $this -> get_dimension_rows_for_month ( $selected_month , 'country' , $top_rows );
$monthly_device_rows = $this -> get_dimension_rows_for_month ( $selected_month , 'device' , max ( 3 , $top_rows ));
$monthly_browser_rows = $this -> get_dimension_rows_for_month ( $selected_month , 'browser' , $top_rows );
$monthly_os_rows = $this -> get_dimension_rows_for_month ( $selected_month , 'os' , $top_rows );
?>
< div class = " wrap ansico-stat-admin-page " >
< h1 >< ? php echo esc_html__ ( 'Ansico Stat Plugin — Monthly statistics' , 'ansico-stat-plugin' ); ?> </h1>
< p >< ? php echo esc_html__ ( 'This page shows monthly rankings, referral source breakdowns, and visitor device details.' , 'ansico-stat-plugin' ); ?> </p>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Daily total views (last 60 days)' , 'ansico-stat-plugin' ); ?> </h2>
< ? php echo $this -> render_chart_markup ( $chart_data , 'ansico-stat-admin-chart' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< div class = " ansico-stat-card-header " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Monthly top %d views' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< a class = " button button-secondary " href = " <?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=ansico_stat_export_csv'), 'ansico_stat_export_csv')); ?> " >< ? php echo esc_html__ ( 'Export CSV' , 'ansico-stat-plugin' ); ?> </a>
</ div >
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< ? php $this -> render_top_posts_table ( $monthly_rows , __ ( 'Monthly views' , 'ansico-stat-plugin' )); ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Monthly top %d 404 visitors' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< ? php $this -> render_top_404_table ( $monthly_404_rows ); ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Monthly referred visitors' , 'ansico-stat-plugin' ); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< div class = " ansico-stat-two-column " >
< div >
< ? php $this -> render_referral_summary_table ( $referral_summary ); ?>
</ div >
< div >
< ? php echo $this -> render_pie_chart_markup ( $pie_summary , 'ansico-monthly-referral-pie' , __ ( 'Monthly referred visitors' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
</ div >
< div style = " margin-top:1.5rem; display:grid; gap:1.5rem; " >
< div >< ? php $this -> render_referral_sources_table ( $search_referrals , __ ( 'Search engine referral URLs' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php $this -> render_referral_sources_table ( $social_referrals , __ ( 'Social media referral URLs' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php $this -> render_referral_sources_table ( $website_referrals , __ ( 'Other website referral URLs' , 'ansico-stat-plugin' )); ?> </div>
</ div >
</ div >
< ? php if ( ! $this -> is_unknown_only_dimension_rows ( $monthly_country_rows )) : ?>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Monthly countries (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< p class = " description " >< ? php echo esc_html__ ( 'Country detection depends on country headers provided by your server or CDN, such as Cloudflare. This plugin does not perform IP geolocation by itself.' , 'ansico-stat-plugin' ); ?> </p>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $monthly_country_rows , __ ( 'Country' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $monthly_country_rows , 'ansico-monthly-country-chart' , __ ( 'Monthly countries' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< ? php endif ; ?>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Monthly device types' , 'ansico-stat-plugin' ); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $monthly_device_rows , __ ( 'Device type' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_pie_chart_markup ( array_column ( $monthly_device_rows , 'total_views' , 'dimension_value' ), 'ansico-monthly-device-pie' , __ ( 'Monthly device types' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Monthly browsers (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $monthly_browser_rows , __ ( 'Browser' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $monthly_browser_rows , 'ansico-monthly-browser-chart' , __ ( 'Monthly browsers' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Monthly operating systems (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_month_navigation ( $selected_month ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $monthly_os_rows , __ ( 'Operating system' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $monthly_os_rows , 'ansico-monthly-os-chart' , __ ( 'Monthly operating systems' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
</ div >
< ? php
}
public function render_yearly_stats_page () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to access this page.' , 'ansico-stat-plugin' ));
}
$settings = $this -> get_settings ();
$top_rows = max ( 1 , min ( 100 , ( int ) $settings [ 'top_list_rows' ]));
$selected_year = $this -> get_selected_year ();
$yearly_rows = $this -> get_yearly_top_posts ( $selected_year , $top_rows );
$yearly_country_rows = $this -> get_dimension_rows_for_year ( $selected_year , 'country' , $top_rows );
$yearly_device_rows = $this -> get_dimension_rows_for_year ( $selected_year , 'device' , max ( 3 , $top_rows ));
$yearly_browser_rows = $this -> get_dimension_rows_for_year ( $selected_year , 'browser' , $top_rows );
$yearly_os_rows = $this -> get_dimension_rows_for_year ( $selected_year , 'os' , $top_rows );
$yearly_referral_summary = $this -> get_referral_summary_for_year ( $selected_year );
$yearly_month_chart = $this -> get_monthly_totals_for_year ( $selected_year );
?>
< div class = " wrap ansico-stat-admin-page " >
< h1 >< ? php echo esc_html__ ( 'Ansico Stat Plugin — Yearly statistics' , 'ansico-stat-plugin' ); ?> </h1>
< p >< ? php echo esc_html__ ( 'This page shows yearly rankings and yearly visitor technology and country breakdowns.' , 'ansico-stat-plugin' ); ?> </p>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Yearly view totals by month' , 'ansico-stat-plugin' ); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< ? php echo $this -> render_bar_chart_markup ( $yearly_month_chart , 'ansico-yearly-month-bar' , __ ( 'Yearly views by month' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Yearly top %d views' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< ? php $this -> render_top_posts_table ( $yearly_rows , __ ( 'Yearly views' , 'ansico-stat-plugin' )); ?>
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Yearly referred visitors' , 'ansico-stat-plugin' ); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< div class = " ansico-stat-two-column " >
< div >
< ? php $this -> render_referral_summary_table ([
'direct' => ( int ) ( $yearly_referral_summary [ 'Direct' ] ? ? 0 ),
'search' => ( int ) ( $yearly_referral_summary [ 'Search engines' ] ? ? 0 ),
'social' => ( int ) ( $yearly_referral_summary [ 'Social media' ] ? ? 0 ),
'website' => ( int ) ( $yearly_referral_summary [ 'Other websites' ] ? ? 0 ),
]); ?>
</ div >
< div >< ? php echo $this -> render_pie_chart_markup ( $yearly_referral_summary , 'ansico-yearly-referral-pie' , __ ( 'Yearly referred visitors' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< ? php if ( ! $this -> is_unknown_only_dimension_rows ( $yearly_country_rows )) : ?>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Yearly countries (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $yearly_country_rows , __ ( 'Country' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $yearly_country_rows , 'ansico-yearly-country-chart' , __ ( 'Yearly countries' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< ? php endif ; ?>
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html__ ( 'Yearly device types' , 'ansico-stat-plugin' ); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $yearly_device_rows , __ ( 'Device type' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_pie_chart_markup ( array_column ( $yearly_device_rows , 'total_views' , 'dimension_value' ), 'ansico-yearly-device-pie' , __ ( 'Yearly device types' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Yearly browsers (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $yearly_browser_rows , __ ( 'Browser' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $yearly_browser_rows , 'ansico-yearly-browser-chart' , __ ( 'Yearly browsers' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
< div class = " ansico-stat-card " >
< h2 >< ? php echo esc_html ( sprintf ( __ ( 'Yearly operating systems (top %d)' , 'ansico-stat-plugin' ), ( int ) $top_rows )); ?> </h2>
< ? php $this -> render_year_navigation ( $selected_year ); ?>
< div class = " ansico-stat-two-column " >
< div >< ? php $this -> render_dimension_table ( $yearly_os_rows , __ ( 'Operating system' , 'ansico-stat-plugin' )); ?> </div>
< div >< ? php echo $this -> render_bar_chart_markup ( $yearly_os_rows , 'ansico-yearly-os-chart' , __ ( 'Yearly operating systems' , 'ansico-stat-plugin' )); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</ div >
</ div >
</ div >
< ? php
}
2026-04-17 21:56:44 +00:00
public function handle_export_single_csv () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to export statistics.' , 'ansico-stat-plugin' ));
}
check_admin_referer ( 'ansico_stat_export_single_csv' );
$requested_url = isset ( $_GET [ 'target_url' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'target_url' ])) : '' ;
$result = $this -> resolve_single_stats_target ( $requested_url );
if ( empty ( $result [ 'success' ]) || empty ( $result [ 'target' ])) {
wp_die ( esc_html__ ( 'No tracked page could be resolved for export.' , 'ansico-stat-plugin' ));
}
$target = ( array ) $result [ 'target' ];
$start_date = isset ( $_GET [ 'start_date' ]) ? $this -> sanitize_stats_date (( string ) wp_unslash ( $_GET [ 'start_date' ])) : '' ;
$end_date = isset ( $_GET [ 'end_date' ]) ? $this -> sanitize_stats_date (( string ) wp_unslash ( $_GET [ 'end_date' ])) : '' ;
if ( $start_date === '' ) {
$start_date = $this -> date_days_ago ( 29 );
}
if ( $end_date === '' ) {
$end_date = current_time ( 'Y-m-d' );
}
if ( strtotime ( $start_date ) > strtotime ( $end_date )) {
[ $start_date , $end_date ] = [ $end_date , $start_date ];
}
$daily_rows = $this -> get_target_daily_chart_data_between ( $target , $start_date , $end_date , 'views' );
$range_days = max ( 1 , ( int ) floor (( strtotime ( $end_date ) - strtotime ( $start_date )) / DAY_IN_SECONDS ) + 1 );
$compare_7 = $this -> get_target_period_comparison ( $target , 7 );
$compare_30 = $this -> get_target_period_comparison ( $target , 30 );
$compare_range = $this -> get_target_period_comparison ( $target , $range_days );
$unique_compare_7 = $this -> get_target_unique_period_comparison ( $target , 7 );
$unique_compare_30 = $this -> get_target_unique_period_comparison ( $target , 30 );
$unique_compare_range = $this -> get_target_unique_period_comparison ( $target , $range_days );
$filename_slug = sanitize_title (( string ) ( $target [ 'label' ] ? ? 'page-stats' ));
if ( $filename_slug === '' ) {
$filename_slug = 'page-stats' ;
}
$filename = 'ansico-single-page-' . $filename_slug . '-' . wp_date ( 'Y-m-d-H-i-s' ) . '.csv' ;
nocache_headers ();
header ( 'Content-Type: text/csv; charset=utf-8' );
header ( 'Content-Disposition: attachment; filename=' . $filename );
$output = fopen ( 'php://output' , 'w' );
fputcsv ( $output , [ 'Selected URL' , ( string ) ( $target [ 'url' ] ? ? '' )]);
fputcsv ( $output , [ 'Label' , ( string ) ( $target [ 'label' ] ? ? '' )]);
fputcsv ( $output , [ 'Type' , ( string ) (( $target [ 'kind' ] ? ? '' ) === 'singular' ? ( $target [ 'post_type' ] ? ? 'post' ) : ( $target [ 'page_type' ] ? ? 'page' ))]);
fputcsv ( $output , [ 'Selected period start' , $start_date ]);
fputcsv ( $output , [ 'Selected period end' , $end_date ]);
fputcsv ( $output , [ 'Selected period views' , ( int ) $this -> get_target_period_views ( $target , $start_date , $end_date )]);
fputcsv ( $output , [ 'Selected period unique visitors' , ( int ) $this -> get_target_period_unique_visitors ( $target , $start_date , $end_date )]);
fputcsv ( $output , [ 'Previous comparable period views' , ( int ) ( $compare_range [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Previous comparable period unique visitors' , ( int ) ( $unique_compare_range [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Lifetime views' , ( int ) $this -> get_target_lifetime_views ( $target )]);
fputcsv ( $output , [ 'Views today' , ( int ) $this -> get_target_period_views ( $target , current_time ( 'Y-m-d' ), current_time ( 'Y-m-d' ))]);
fputcsv ( $output , [ 'Unique visitors today' , ( int ) $this -> get_target_period_unique_visitors ( $target , current_time ( 'Y-m-d' ), current_time ( 'Y-m-d' ))]);
fputcsv ( $output , [ 'Views last 7 days' , ( int ) ( $compare_7 [ 'current_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Unique visitors last 7 days' , ( int ) ( $unique_compare_7 [ 'current_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Previous 7 days' , ( int ) ( $compare_7 [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Previous unique visitors 7 days' , ( int ) ( $unique_compare_7 [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Views last 30 days' , ( int ) ( $compare_30 [ 'current_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Unique visitors last 30 days' , ( int ) ( $unique_compare_30 [ 'current_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Previous 30 days' , ( int ) ( $compare_30 [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , [ 'Previous unique visitors 30 days' , ( int ) ( $unique_compare_30 [ 'previous_views' ] ? ? 0 )]);
fputcsv ( $output , []);
fputcsv ( $output , [ 'Date' , 'Views' , 'Unique visitors' ]);
foreach ( $daily_rows as $row ) {
fputcsv ( $output , [
( string ) ( $row [ 'date' ] ? ? '' ),
( int ) ( $row [ 'views' ] ? ? 0 ),
( int ) $this -> get_target_period_unique_visitors ( $target , ( string ) ( $row [ 'date' ] ? ? '' ), ( string ) ( $row [ 'date' ] ? ? '' )),
]);
}
fclose ( $output );
exit ;
}
2026-04-15 19:53:03 +00:00
public function handle_export_csv () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to export statistics.' , 'ansico-stat-plugin' ));
}
check_admin_referer ( 'ansico_stat_export_csv' );
$filename = 'ansico-stats-' . wp_date ( 'Y-m-d-H-i-s' ) . '.csv' ;
$install_month = $this -> get_install_month_key ();
$current_month = wp_date ( 'Y-m' , current_time ( 'timestamp' ));
nocache_headers ();
header ( 'Content-Type: text/csv; charset=utf-8' );
header ( 'Content-Disposition: attachment; filename=' . $filename );
$output = fopen ( 'php://output' , 'w' );
fputcsv ( $output , [ 'Period type' , 'Period' , 'Rank' , 'Title' , 'Post ID' , 'Post Type' , 'Views' , 'Lifetime Views' , 'Permalink' ]);
$cursor = strtotime ( $install_month . '-01' );
$end = strtotime ( $current_month . '-01' );
while ( $cursor <= $end ) {
$month_key = wp_date ( 'Y-m' , $cursor );
$rows = $this -> get_monthly_top_posts ( $month_key , 10 );
foreach ( $rows as $index => $row ) {
$post_id = ( int ) $row [ 'post_id' ];
fputcsv ( $output , [
'month' ,
$month_key ,
$index + 1 ,
get_the_title ( $post_id ) ? : '(no title)' ,
$post_id ,
get_post_type ( $post_id ),
( int ) $row [ 'period_views' ],
( int ) get_post_meta ( $post_id , self :: TOTAL_META_KEY , true ),
get_permalink ( $post_id ),
]);
}
$cursor = strtotime ( '+1 month' , $cursor );
}
for ( $year = $this -> get_install_year (); $year <= ( int ) wp_date ( 'Y' , current_time ( 'timestamp' )); $year ++ ) {
$rows = $this -> get_yearly_top_posts ( $year , 10 );
foreach ( $rows as $index => $row ) {
$post_id = ( int ) $row [ 'post_id' ];
fputcsv ( $output , [
'year' ,
$year ,
$index + 1 ,
get_the_title ( $post_id ) ? : '(no title)' ,
$post_id ,
get_post_type ( $post_id ),
( int ) $row [ 'period_views' ],
( int ) get_post_meta ( $post_id , self :: TOTAL_META_KEY , true ),
get_permalink ( $post_id ),
]);
}
}
fclose ( $output );
exit ;
}
public function handle_save_settings () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to save these settings.' , 'ansico-stat-plugin' ));
}
check_admin_referer ( 'ansico_stat_save_settings' );
$allowed_post_types = $this -> get_default_track_post_types ();
$submitted_post_types = isset ( $_POST [ 'track_post_types' ]) && is_array ( $_POST [ 'track_post_types' ]) ? array_map ( 'sanitize_key' , wp_unslash ( $_POST [ 'track_post_types' ])) : [];
$submitted_post_types = array_values ( array_intersect ( $allowed_post_types , $submitted_post_types ));
$visitor_counting_mode = isset ( $_POST [ 'visitor_counting_mode' ]) ? sanitize_key ( wp_unslash ( $_POST [ 'visitor_counting_mode' ])) : 'count_all' ;
if ( ! in_array ( $visitor_counting_mode , [ 'count_all' , 'exclude_admins' , 'exclude_all_logged_in' ], true )) {
$visitor_counting_mode = 'count_all' ;
}
$settings = [
'visitor_counting_mode' => $visitor_counting_mode ,
'exclude_known_bots' => isset ( $_POST [ 'exclude_known_bots' ]) ? 1 : 0 ,
'top_list_rows' => isset ( $_POST [ 'top_list_rows' ]) ? max ( 1 , min ( 100 , ( int ) $_POST [ 'top_list_rows' ])) : 10 ,
'referral_rows' => isset ( $_POST [ 'referral_rows' ]) ? max ( 1 , min ( 200 , ( int ) $_POST [ 'referral_rows' ])) : 25 ,
'revisit_minutes' => isset ( $_POST [ 'revisit_minutes' ]) ? max ( 1 , min ( 10080 , ( int ) $_POST [ 'revisit_minutes' ])) : 30 ,
'frontend_label' => isset ( $_POST [ 'frontend_label' ]) ? sanitize_text_field ( wp_unslash ( $_POST [ 'frontend_label' ])) : 'Views' ,
'track_post_types' => $submitted_post_types ,
'track_front_page' => isset ( $_POST [ 'track_front_page' ]) ? 1 : 0 ,
'track_posts_page' => isset ( $_POST [ 'track_posts_page' ]) ? 1 : 0 ,
'track_archives' => isset ( $_POST [ 'track_archives' ]) ? 1 : 0 ,
'track_search' => isset ( $_POST [ 'track_search' ]) ? 1 : 0 ,
'track_404' => isset ( $_POST [ 'track_404' ]) ? 1 : 0 ,
];
update_option ( self :: OPTION_KEY , $settings , false );
wp_safe_redirect ( admin_url ( 'admin.php?page=' . self :: MENU_SLUG_SETTINGS . '&settings-updated=true' ));
exit ;
}
public function handle_reset_all_views () {
if ( ! current_user_can ( 'manage_options' )) {
wp_die ( esc_html__ ( 'You do not have permission to reset statistics.' , 'ansico-stat-plugin' ));
}
check_admin_referer ( 'ansico_stat_reset_all' );
global $wpdb ;
$wpdb -> query ( $wpdb -> prepare (
" DELETE FROM { $wpdb -> postmeta } WHERE meta_key = %s " ,
self :: TOTAL_META_KEY
));
$wpdb -> query ( 'TRUNCATE TABLE ' . self :: daily_table_name ());
$wpdb -> query ( 'TRUNCATE TABLE ' . self :: post_daily_table_name ());
$wpdb -> query ( 'TRUNCATE TABLE ' . self :: page_daily_table_name ());
$wpdb -> query ( 'TRUNCATE TABLE ' . self :: referral_table_name ());
$wpdb -> query ( 'TRUNCATE TABLE ' . self :: dimension_table_name ());
update_option ( self :: INSTALL_OPTION_KEY , current_time ( 'Y-m-d' ), false );
wp_safe_redirect ( admin_url ( 'admin.php?page=' . self :: MENU_SLUG_SETTINGS . '&reset=success' ));
exit ;
}
public function register_admin_columns () {
if ( ! is_admin ()) {
return ;
}
$post_types = get_post_types ([ 'show_ui' => true ], 'names' );
foreach ( $post_types as $post_type ) {
add_filter ( " manage_ { $post_type } _posts_columns " , [ $this , 'add_view_columns' ]);
add_action ( " manage_ { $post_type } _posts_custom_column " , [ $this , 'render_view_columns' ], 10 , 2 );
add_filter ( " manage_edit- { $post_type } _sortable_columns " , [ $this , 'register_sortable_view_columns' ]);
}
add_filter ( 'manage_pages_columns' , [ $this , 'add_view_columns' ]);
add_action ( 'manage_pages_custom_column' , [ $this , 'render_view_columns' ], 10 , 2 );
add_filter ( 'manage_edit-page_sortable_columns' , [ $this , 'register_sortable_view_columns' ]);
}
public function add_view_columns ( $columns ) {
$new_columns = [];
foreach ( $columns as $key => $label ) {
$new_columns [ $key ] = $label ;
if ( $key === 'title' ) {
$new_columns [ 'ansico_lifetime_views' ] = __ ( 'Post Views (Lifetime)' , 'ansico-stat-plugin' );
$new_columns [ 'ansico_latest_month_views' ] = __ ( 'Post Views (Latest Month)' , 'ansico-stat-plugin' );
}
}
if ( ! isset ( $new_columns [ 'ansico_lifetime_views' ])) {
$new_columns [ 'ansico_lifetime_views' ] = __ ( 'Post Views (Lifetime)' , 'ansico-stat-plugin' );
$new_columns [ 'ansico_latest_month_views' ] = __ ( 'Post Views (Latest Month)' , 'ansico-stat-plugin' );
}
return $new_columns ;
}
public function render_view_columns ( $column , $post_id ) {
if ( $column === 'ansico_lifetime_views' ) {
echo esc_html ( number_format_i18n (( int ) get_post_meta ( $post_id , self :: TOTAL_META_KEY , true )));
}
if ( $column === 'ansico_latest_month_views' ) {
echo esc_html ( number_format_i18n ( $this -> get_post_views_for_latest_month (( int ) $post_id )));
}
}
public function register_sortable_view_columns ( $columns ) {
$columns [ 'ansico_lifetime_views' ] = 'ansico_lifetime_views' ;
$columns [ 'ansico_latest_month_views' ] = 'ansico_latest_month_views' ;
return $columns ;
}
public function render_admin_view_filter () {
global $typenow , $pagenow ;
if ( ! is_admin () || $pagenow !== 'edit.php' || empty ( $typenow )) {
return ;
}
$post_type_object = get_post_type_object ( $typenow );
if ( ! $post_type_object || empty ( $post_type_object -> show_ui )) {
return ;
}
$selected = isset ( $_GET [ 'ansico_view_filter' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'ansico_view_filter' ])) : '' ;
?>
< select name = " ansico_view_filter " >
< option value = " " >< ? php echo esc_html__ ( 'View stats filter' , 'ansico-stat-plugin' ); ?> </option>
< option value = " lifetime_desc " < ? php selected ( $selected , 'lifetime_desc' ); ?> ><?php echo esc_html__('Most viewed (lifetime)', 'ansico-stat-plugin'); ?></option>
< option value = " lifetime_asc " < ? php selected ( $selected , 'lifetime_asc' ); ?> ><?php echo esc_html__('Least viewed (lifetime)', 'ansico-stat-plugin'); ?></option>
< option value = " month_desc " < ? php selected ( $selected , 'month_desc' ); ?> ><?php echo esc_html__('Most viewed (latest month)', 'ansico-stat-plugin'); ?></option>
< option value = " month_asc " < ? php selected ( $selected , 'month_asc' ); ?> ><?php echo esc_html__('Least viewed (latest month)', 'ansico-stat-plugin'); ?></option>
</ select >
< ? php
}
public function handle_admin_list_sorting ( $query ) {
if ( ! is_admin () || ! $query -> is_main_query ()) {
return ;
}
$view_filter = isset ( $_GET [ 'ansico_view_filter' ]) ? sanitize_text_field ( wp_unslash ( $_GET [ 'ansico_view_filter' ])) : '' ;
if ( $view_filter === 'lifetime_desc' || $view_filter === 'lifetime_asc' ) {
$query -> set ( 'meta_key' , self :: TOTAL_META_KEY );
$query -> set ( 'orderby' , 'meta_value_num' );
$query -> set ( 'order' , $view_filter === 'lifetime_asc' ? 'ASC' : 'DESC' );
}
if ( $view_filter === 'month_desc' || $view_filter === 'month_asc' ) {
$query -> set ( 'ansico_sort_latest_month_views' , 1 );
$query -> set ( 'order' , $view_filter === 'month_asc' ? 'ASC' : 'DESC' );
}
$orderby = $query -> get ( 'orderby' );
if ( $orderby === 'ansico_lifetime_views' ) {
$query -> set ( 'meta_key' , self :: TOTAL_META_KEY );
$query -> set ( 'orderby' , 'meta_value_num' );
}
if ( $orderby === 'ansico_latest_month_views' ) {
$query -> set ( 'ansico_sort_latest_month_views' , 1 );
}
}
public function add_monthly_views_sorting_clauses ( $clauses , $query ) {
if ( ! is_admin () || ! $query -> is_main_query () || ! $query -> get ( 'ansico_sort_latest_month_views' )) {
return $clauses ;
}
global $wpdb ;
$table = self :: post_daily_table_name ();
$range = $this -> get_latest_month_range ();
$order = strtoupper (( string ) $query -> get ( 'order' )) === 'ASC' ? 'ASC' : 'DESC' ;
$join_alias = 'ansico_monthly_views' ;
if ( strpos ( $clauses [ 'join' ], " AS { $join_alias } " ) === false ) {
$clauses [ 'join' ] .= $wpdb -> prepare (
" LEFT JOIN (
SELECT post_id , SUM ( views ) AS month_views
FROM { $table }
WHERE stat_date BETWEEN % s AND % s
GROUP BY post_id
) AS { $join_alias } ON { $wpdb -> posts } . ID = { $join_alias } . post_id " ,
$range [ 'start' ],
$range [ 'end' ]
);
}
$clauses [ 'orderby' ] = " COALESCE( { $join_alias } .month_views, 0) { $order } , { $wpdb -> posts } .post_date DESC " ;
return $clauses ;
}
}
new Ansico_Stat_Plugin ();
}