=== VisitStats ===
Contributors: james333
Tags: analytics, statistics, privacy, gdpr, cookieless
Requires at least: 6.2
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 1.1.6
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Privacy-first site analytics for self-hosted sites. No cookies, no Google, no data leaves your server. Stats live in your own database.

== Description ==

**VisitStats** is a lightweight, privacy-first analytics plugin for WordPress. All your data stays in your own WordPress database — we never see it, no third party sees it.

**Why VisitStats?**

* **No cookies, no consent banner needed.** Tracking is GDPR-friendly by default.
* **Self-hosted.** All data lives in your WordPress database. No external service, no Automattic, no Google.
* **Lightweight.** Frontend tracker is under 2KB. Loads asynchronously. Won't slow your site.
* **Zero setup.** Install, activate, done. No theme edits, no tag manager, no JS snippets.
* **Built for WordPress.** Reports appear inside your WordPress dashboard — no separate app to log into.

**Recommended for sites with up to ~10,000 daily pageviews** (around 95% of WordPress sites). For very high-traffic sites, consider dedicated analytics services.

**What you get (all free, all the time):**

* Real-time visitor count — see who's on your site right now
* Pageviews, unique visitors, sessions
* Top pages
* Top traffic sources (direct, search, social, AI, referral)
* Top referrers — actual URLs sending you traffic
* Device breakdown (desktop, mobile, tablet)
* Browser & operating system detection
* Configurable data retention from 1 day up to 1 year
* Date range views: today, 7 days, 30 days, 90 days, 1 year
* Engagement signals (scroll depth, time on page)
* Cookieless visitor identification using daily-rotating salt + SHA-256 hashing
* Bot filtering with built-in bot signature detection
* Optional administrator / logged-in user exclusion
* Honors Do Not Track browser signal
* Automatic data pruning to keep your database lean
* Rate-limited tracking endpoint protects against abuse
* IP anonymization before hashing — actual IPs never stored

**Privacy by design**

VisitStats never:

* Sets cookies
* Stores IP addresses
* Sends data to third parties
* Fingerprints visitors
* Tracks across days

Visitor identification uses a daily-rotating salt hashed with the (anonymized) IP and User Agent to count unique visits within a 24-hour window. The salt regenerates every 24 hours, making cross-day tracking impossible. This is the same approach used by Plausible Analytics and Fathom Analytics.

**For developers**

* Clean REST API at `/wp-json/visitstats/v1/`
* Filter hooks for customizing behavior (rate limiting, bot detection, etc.)
* PSR-style class organization
* No external dependencies — pure WordPress core

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/` or install via the WordPress plugin directory.
2. Activate the plugin through the 'Plugins' menu in WordPress.
3. Visit the new "VisitStats" item in your admin sidebar to see your dashboard.
4. Tracking starts automatically — no further configuration required.

== Frequently Asked Questions ==

= Is this really GDPR-friendly? =

VisitStats is designed to minimize GDPR exposure: no cookies, no IP storage, no fingerprinting, and data stays on your own server. We don't claim "compliance" as a legal certainty (that depends on your processing context), but we provide the technical foundations that make compliance achievable without a cookie banner.

= Will this slow down my site? =

For sites up to ~10,000 daily pageviews, no measurable impact. The tracker is under 2KB and uses `navigator.sendBeacon()` so requests never block page interactions. For sites consistently above 10K daily pageviews, we recommend dedicated analytics services.

= Where is my data stored? =

In your own WordPress database. We never see it. There is no cloud component, no SaaS layer, no external service.

= Will my database get huge? =

No. The plugin includes automatic pruning. Raw events are pruned at your configured retention window (default 30 days). Daily aggregates are kept separately and are tiny — one row per day per metric.

= Does it work with caching plugins? =

Yes. The tracker uses a standard WordPress script and a REST endpoint. Caching plugins won't break it.

= Can I run this alongside Google Analytics? =

Yes. They don't conflict. Many users run both during migration.

= Does it work with Cloudflare or a reverse proxy? =

In its current form, the plugin reads `REMOTE_ADDR` for visitor identification. Sites behind Cloudflare or a custom reverse proxy will see all visitors hashed identically, which affects unique-visitor counts. Support for proxy headers is on the roadmap — submit feedback from the plugin's "Feedback" page to prioritize it.

= How can I request features? =

There's a "Feedback" page inside the plugin with a feature voting form. The most-requested items get worked on first.

== Screenshots ==

1. The main dashboard with real-time visitors, summary cards, and pageviews chart.
2. Top pages, sources, devices, and browsers reports.
3. Top referrers showing the actual external URLs sending traffic.
4. Settings page with tracking, exclusions, and retention controls.
5. Feedback page for feature requests and bug reports.

== Changelog ==

= 1.1.6 =
* Properly fixed the PHP 8.1+ ltrim() deprecation: the JavaScript tracker was sending `referrer: null` for direct visits, and WordPress's REST sanitize callbacks ran `esc_url_raw(null)` before our PHP code could intercept. Now the tracker sends empty string, and we use custom null-safe sanitize callbacks server-side as defense in depth.

= 1.1.5 =
* Fixed PHP 8.1+ deprecation warnings when null was passed to esc_url_raw() and ltrim() for direct visits (visitors arriving with no referrer). All URL/path/referrer values are now coerced to strings before being passed to WordPress escaping helpers.
* Added explicit defaults to REST API parameter definitions so null never reaches the sanitize callback.
* Hardened sanitize_path() against null and non-string input.

= 1.1.4 =
* Renamed an internal rate-limit transient key to use the plugin's `visitstats_` prefix (was `ip_rl_`). No user-visible changes.

= 1.1.3 =
* Improved phpcs annotation placement so static analysis tools (Plugin Check, WPCS) correctly recognize the documented exceptions for schema operations in uninstaller and dbDelta in activator. No functional changes.

= 1.1.2 =
* Updated "Tested up to" to WordPress 7.0.

= 1.1.1 =
* Polish pass before WordPress.org submission.
* Softened description copy to reduce potential trademark concerns.
* No functional changes.

= 1.1.0 =
* Renamed from a previous name to "VisitStats" for a cleaner, more descriptive identity.
* Made all features unconditionally free and fully functional, matching WordPress.org Plugin Directory guidelines.
* Data retention now user-configurable from 1 to 365 days (was previously capped).
* All date ranges (today, 7d, 30d, 90d, 1 year) available to everyone.
* Engagement / scroll-depth tracking now works for all users.
* Moved admin menu from position 3 to position 81 (under Settings) to respect WordPress core admin hierarchy.
* Replaced upsell page with a Feedback & Feature Requests page.

= 1.0.x =
* Earlier iterations under previous naming. See full history in the repository.

== Upgrade Notice ==

= 1.1.1 =
Minor polish — softened description copy. No functional changes.

= 1.1.0 =
Major rename and policy change: all features now free, retention configurable up to 365 days, menu repositioned.
