/* Plugin Name: Stickeey Analytics Suite for WooCommerce Description: Product Visitor Stats, Cart Product Tracker, and Search Terms Tracker — accurate, lightweight, and GDPR‑aware. Version: 1.0.0 Author: Stickeey Requires at least: 6.0 Tested up to: 6.6 Requires PHP: 7.4 License: GPLv2 or later Text Domain: sasw */ // === Safety guard: prevent direct access === if ( ! defined( 'ABSPATH' ) ) { exit; } // === Constants === define( 'SASW_VERSION', '1.0.0' ); define( 'SASW_DIR', plugin_dir_path( __FILE__ ) ); define( 'SASW_URL', plugin_dir_url( __FILE__ ) ); global $sasw_db_version; $sasw_db_version = '1'; // === Activation / Deactivation === register_activation_hook( __FILE__, function(){ global $wpdb; $charset = $wpdb->get_charset_collate(); $table = $wpdb->prefix . 'sasw_events'; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $sql = "CREATE TABLE {$table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, event_type VARCHAR(20) NOT NULL, -- view|add|remove|purchase|search product_id BIGINT UNSIGNED NULL, user_id BIGINT UNSIGNED NULL, session_key VARCHAR(64) NULL, ip_hash CHAR(64) NULL, ua_hash CHAR(64) NULL, country VARCHAR(64) NULL, device VARCHAR(16) NULL, qty INT NULL, order_id BIGINT UNSIGNED NULL, search_term TEXT NULL, referrer TEXT NULL, duration_sec INT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), INDEX idx_type_created (event_type, created_at), INDEX idx_product_created (product_id, created_at), INDEX idx_order (order_id), INDEX idx_session (session_key) ) {$charset};"; dbmDelta( $sql ); } ); // === Session Key (cookie) === add_action('init', function(){ if ( is_admin() ) return; $cookie = 'sasw_sid'; if ( empty( $_COOKIE[$cookie] ) ) { $val = wp_generate_uuid4(); // 12 hours session window setcookie( $cookie, $val, time()+ 12*HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); $_COOKIE[$cookie] = $val; } }); // === Helpers === function sasw_now_utc(){ return gmdate('Y-m-d H:i:s'); } function sasw_client_country(){ // Prefer Cloudflare header if present; fallback unknown. if ( ! empty($_SERVER['HTTP_CF_IPCOUNTRY']) ) return sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); return 'Unknown'; } function sasw_device(){ return wp_is_mobile() ? 'Mobile' : 'Desktop'; } function sasw_hash($val){ return hash('sha256', $val . AUTH_SALT); } function sasw_session_key(){ return isset($_COOKIE['sasw_sid']) ? sanitize_text_field($_COOKIE['sasw_sid']) : null; } function sasw_referrer(){ return isset($_SERVER['HTTP_REFERER']) ? esc_url_raw($_SERVER['HTTP_REFERER']) : ''; } // === Logger === function sasw_log_event( $args ){ global $wpdb; $table = $wpdb->prefix+'sasw_events'; $defaults = [ 'event_type' => '', 'product_id' => null, 'user_id' => get_current_user_id() ?: null, 'session_key' => sasw_session_key(), 'ip_hash' => sasw_hash($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'), 'ua_hash' => sasw_hash($_SERVER['HTTP_USER_AGENT'] ?? ''), 'country' => sasw_client_country(), 'device' => sasw_device(), 'qty' => null, 'order_id' => null, 'search_term' => null, 'referrer' => sasw_referrer(), 'duration_sec' => null, 'created_at' => sasw_now_utc(), ]; $data = wp_parse_args( $args, $defaults ); $data = array_map( function($v){ return is_string($v) ? wp_kses_post( $v ) : $v; }, $data ); $wpdb->insert( $table, $data ); } // === REST endpoint for precise product view duration === add_action('rest_api_init', function(){ register_rest_route('sasw/v1', '/track', [ 'methods' => 'POST', 'permission_callback' => function(){ return wp_verify_nonce( $_POST['_wpnonce'] ?? '', 'sasw_track' ); }, 'callback' => function(WP_REST_Request $req){ $product_id = intval( $req->get_param('product_id') ); $duration = intval( $req->get_param('duration_sec') ); if ( $product_id <= 0 || $duration < 0 ) return new WP_REST_Response(['ok'=>false], 400); $session = sasw_session_key(); // De‑dup guard: record at most once per product per session within 30 minutes unless duration increased by ≥5s global $wpdb; $table = $wpdb->prefix.'sasw_events'; $recent = $wpdb->get_row( $wpdb->prepare("SELECT id, duration_sec, created_at FROM {$table} WHERE event_type='view' AND product_id=%d AND session_key=%s ORDER BY id DESC LIMIT 1", $product_id, $session) ); $record = true; if ( $recent ){ $recent_ts = strtotime($recent->created_at.' UTC'); if ( time() - $recent_ts < 30*MINUTE_IN_SECONDS && $duration <= intval($recent->duration_sec)+4 ) { $record = false; // likely reload/duplicate } } if ( $record ){ sasw_log_event([ 'event_type' => 'view', 'product_id' => $product_id, 'duration_sec' => $duration, ]); } return ['ok'=>true]; } ]); }); // === Frontend JS: only on single product === add_action('wp_enqueue_scripts', function(){ if ( ! function_exists('is_product') || ! is_product() ) return; global $post; $product_id = $post ? intval($post->ID) : 0; if ( ! $product_id ) return; wp_register_script('sasw-track', SASW_URL.'assets/sasw-track.js', [], SASW_VERSION, true); wp_localize_script('sasw-track', 'SASW', [ 'pid' => $product_id, 'endpoint' => esc_url_raw( rest_url('sasw/v1/track') ), 'nonce' => wp_create_nonce('sasw_track'), 'minEngage' => 5, // seconds to consider engaged view ]); wp_enqueue_script('sasw-track'); }); // === Create JS file on the fly if missing (for single‑file plugin installs) === add_action('plugins_loaded', function(){ $path = SASW_DIR.'assets/sasw-track.js'; if ( ! file_exists( dirname($path) ) ) { wp_mkdir_p( dirname($path) ); } if ( ! file_exists( $path ) ){ file_put_contents($path, "(function(){\n if(!window.SASW||!SASW.pid) return;\n var start=Date.now(), sent=false;\n function duration(){ return Math.round((Date.now()-start)/1000); }\n function send(){\n if(sent) return;\n var d=duration();\n if(d<1) return;\n var fd=new FormData();\n fd.append('product_id', SASW.pid);\n fd.append('duration_sec', d);\n fd.append('_wpnonce', SASW.nonce);\n if(navigator.sendBeacon){ navigator.sendBeacon(SASW.endpoint, fd); } else { fetch(SASW.endpoint,{method:'POST',body:fd,credentials:'same-origin'}); }\n sent=true;\n }\n window.addEventListener('beforeunload', send);\n document.addEventListener('visibilitychange', function(){ if(document.visibilityState==='hidden'){ send(); } });\n setTimeout(send, 20*60*1000);\n})();"); } }); // === WooCommerce hooks for cart + orders === add_action('woocommerce_add_to_cart', function($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data){ sasw_log_event([ 'event_type' => 'add', 'product_id' => intval($variation_id ?: $product_id), 'qty' => intval($quantity), ]); }, 10, 6); add_action('woocommerce_remove_cart_item', function($cart_key, $cart){ $item = $cart->removed_cart_contents[$cart_key] ?? null; if ( $item ){ $product_id = intval($item['variation_id'] ?: $item['product_id']); sasw_log_event([ 'event_type' => 'remove', 'product_id' => $product_id, 'qty' => intval($item['quantity'] ?? 1), ]); } }, 10, 2); add_action('woocommerce_thankyou', function($order_id){ if ( ! $order_id ) return; $order = wc_get_order($order_id); if ( ! $order ) return; foreach ( $order->get_items() as $item ){ $product_id = intval( $item->get_variation_id() ?: $item->get_product_id() ); sasw_log_event([ 'event_type' => 'purchase', 'product_id' => $product_id, 'qty' => intval( $item->get_quantity() ), 'order_id' => intval($order_id), ]); } }); // === Search terms tracker === add_action('pre_get_posts', function($q){ if ( is_admin() || ! $q->is_main_query() ) return; if ( $q->is_search() ){ $term = sanitize_text_field( get_search_query(false) ); if ( $term !== '' ){ // De‑dup: once per session per unique term per 10 minutes $k = 'sasw_last_search_'.md5($term); $last = isset($_COOKIE[$k]) ? intval($_COOKIE[$k]) : 0; if ( time()-$last > 10*MINUTE_IN_SECONDS ){ sasw_log_event(['event_type'=>'search','search_term'=>$term]); setcookie($k, time(), time()+ 10*MINUTE_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true); } } } }); // === Admin UI === add_action('admin_menu', function(){ add_menu_page( __('Site Stats','sasw'), __('Site Stats','sasw'), 'manage_woocommerce', 'sasw_dashboard', 'sasw_render_dashboard', 'dashicons-chart-line', 56 ); }); function sasw_render_dashboard(){ if ( ! current_user_can('manage_woocommerce') ) return; $tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'visits'; $country = isset($_GET['country']) ? sanitize_text_field($_GET['country']) : 'all'; $engaged = isset($_GET['engaged']) ? intval($_GET['engaged']) : 0; $clear = isset($_POST['sasw_clear']) ? sanitize_text_field($_POST['sasw_clear']) : ''; if ( $clear ){ sasw_handle_clear($clear); echo '

Data cleared.

'; } echo '

Site Stats

'; echo '

Product Visitor Stats, Cart Product Tracker, Search Terms Tracker

'; echo ''; echo '
'; echo ''; echo ''; // country filter $countries = sasw_get_countries($tab); echo ' '; if ( $tab==='visits' ){ echo ' '; } echo ' '; echo 'Refresh'; echo '
'; echo '
'; if ( $tab==='visits' ) echo ''; if ( $tab==='cart' ) echo ''; if ( $tab==='search' ) echo ''; echo '
'; if ( $tab==='visits' ) sasw_render_visits_table($country, $engaged); if ( $tab==='cart' ) sasw_render_cart_table($country); if ( $tab==='search' ) sasw_render_search_table($country); echo '
'; } function sasw_get_countries($tab){ global $wpdb; $table=$wpdb->prefix.'sasw_events'; $where = "WHERE 1=1"; if ( $tab==='visits' ) $where .= " AND event_type='view'"; elseif ( $tab==='cart' ) $where .= " AND event_type IN ('add','remove','purchase')"; else $where .= " AND event_type='search'"; $rows = $wpdb->get_col("SELECT DISTINCT country FROM {$table} {$where} ORDER BY country ASC"); return array_values( array_filter($rows) ); } function sasw_handle_clear($type){ global $wpdb; $table=$wpdb->prefix.'sasw_events'; if ( $type==='view' ) $wpdb->query("DELETE FROM {$table} WHERE event_type='view'"); elseif ( $type==='cart' ) $wpdb->query("DELETE FROM {$table} WHERE event_type IN ('add','remove','purchase')"); elseif ( $type==='search' ) $wpdb->query("DELETE FROM {$table} WHERE event_type='search'"); } function sasw_render_visits_table($country, $engaged){ global $wpdb; $table=$wpdb->prefix.'sasw_events'; $where = "WHERE event_type='view'"; $params=[]; if ( $country !== 'all' ){ $where .= " AND country=%s"; $params[]=$country; } if ( $engaged ){ $where .= " AND duration_sec >= 5"; } $query = $wpdb->prepare( "SELECT product_id, country, device, MAX(created_at) as visited_at, MAX(duration_sec) as duration, COUNT(*) as cnt FROM {$table} {$where} GROUP BY product_id, country, device ORDER BY visited_at DESC", $params ); $rows = $wpdb->get_results($query); // header metrics $total = intval( $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE event_type='view'" ) ); $avg = intval( $wpdb->get_var("SELECT AVG(duration_sec) FROM {$table} WHERE event_type='view' AND duration_sec IS NOT NULL" ) ); $top_c = $wpdb->get_row("SELECT country, COUNT(*) c FROM {$table} WHERE event_type='view' GROUP BY country ORDER BY c DESC LIMIT 1"); $top = $top_c ? esc_html($top_c->country).' ('.intval($top_c->c).')' : '—'; echo '

Visits: '.$total.'   Avg Time: '.esc_html($avg).'s   Top Country: '.$top.'

'; echo ''; foreach ( $rows as $r ){ $name = get_the_title($r->product_id) ?: ('#'.$r->product_id); $iphash = $wpdb->get_var( $wpdb->prepare("SELECT ip_hash FROM {$table} WHERE event_type='view' AND product_id=%d ORDER BY id DESC LIMIT 1", $r->product_id) ); echo '' .'' .'' .'' .'' .'' .'' .''; } echo '
Product NameCountryDeviceTime of VisitTime SpentIP (hashed)
'.esc_html($name).''.esc_html($r->country).''.esc_html($r->device).''.esc_html($r->visited_at).''.intval($r->duration).'s'.esc_html(substr($iphash,0,12)).'
'; } function sasw_render_cart_table($country){ global $wpdb; $table=$wpdb->prefix.'sasw_events'; $where = "WHERE event_type IN ('add','remove','purchase')"; $params=[]; if ( $country!=='all'){ $where.=" AND country=%s"; $params[]=$country; } $sql = $wpdb->prepare("SELECT id, event_type, product_id, qty, order_id, country, device, created_at FROM {$table} {$where} ORDER BY id DESC LIMIT 300", $params); $rows = $wpdb->get_results($sql); echo ''; foreach ($rows as $r){ $name = get_the_title($r->product_id) ?: ('#'.$r->product_id); $status = ($r->event_type==='purchase') ? 'Purchased' : (($r->event_type==='remove')?'Removed':'Added'); echo '' .'' .'' .'' .'' .'' .'' .''; } echo '
Product NameCountryDeviceAdded TimeOrder IDStatus
'.esc_html($name).''.esc_html($r->country).''.esc_html($r->device).''.esc_html($r->created_at).''.( $r->order_id ? intval($r->order_id) : '—' ).''.$status.'
'; } function sasw_render_search_table($country){ global $wpdb; $table=$wpdb->prefix.'sasw_events'; $where = "WHERE event_type='search'"; $params=[]; if ( $country!=='all' ){ $where.=" AND country=%s"; $params[]=$country; } $sql = $wpdb->prepare("SELECT search_term, country, device, ip_hash, created_at FROM {$table} {$where} ORDER BY id DESC LIMIT 300", $params); $rows = $wpdb->get_results($sql); echo ''; foreach ($rows as $r){ echo '' .'' .'' .'' .'' .'' .''; } echo '
Search TermCountryDeviceIP (hashed)Searched At
'.esc_html($r->search_term).''.esc_html($r->country).''.esc_html($r->device).''.esc_html(substr($r->ip_hash,0,12)).''.esc_html($r->created_at).'
'; } // === Hardening: remove data on uninstall (optional) === register_uninstall_hook( __FILE__, 'sasw_uninstall' ); function sasw_uninstall(){ if ( ! current_user_can('activate_plugins') ) return; global $wpdb; $table=$wpdb->prefix.'sasw_events'; $wpdb->query("DROP TABLE IF EXISTS {$table}"); }
Warning: Cannot modify header information - headers already sent by (output started at /home/u912512621/domains/stickeey.com/public_html/wp-content/plugins/stickeey-analytics-suite/stickeey-analytics-suite.php:1) in /home/u912512621/domains/stickeey.com/public_html/wp-includes/pluggable.php on line 1450

Warning: Cannot modify header information - headers already sent by (output started at /home/u912512621/domains/stickeey.com/public_html/wp-content/plugins/stickeey-analytics-suite/stickeey-analytics-suite.php:1) in /home/u912512621/domains/stickeey.com/public_html/wp-includes/pluggable.php on line 1453