=== Visitor Traffic Real Time Statistics ===
Contributors: ictworld24
Tags: analytics, statistics, visitors, geolocation, traffic
Requires at least: 5.6
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 1.1.13
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Real-time visitor tracking, geolocation, browser/search-engine breakdown, referrers, top pages, live "online now" counter and more — all inside your WordPress dashboard. No Google Analytics setup, no consent-banner dependency.

== External services ==

This plugin connects to a third-party service to provide visitor geolocation (country/region/city). You can pick which one under *Traffic Stats → Settings → Geolocation provider*:

* **freeipapi.com (default since 1.1.9)** — for every new visitor, the plugin sends that visitor's IP address to freeipapi.com's free JSON endpoint (`https://free.freeipapi.com/api/v1/json/...`) over an encrypted HTTPS connection to look up its approximate location. No API key, account or signup is required, and freeipapi.com's free tier permits commercial use. Each IP's result is cached locally for 7 days (via WordPress transients) so the same visitor is not looked up repeatedly. No cookies, analytics identifiers, or personal data other than the bare IP address are sent.
  freeipapi.com: [https://freeipapi.com/](https://freeipapi.com/) — [Terms of Use](https://freeipapi.com/terms) / [Privacy Policy](https://freeipapi.com/privacy)
* **ip-api.com (optional, legacy)** — the same kind of lookup against ip-api.com's free JSON endpoint (`http://ip-api.com/json/...`) instead. Kept as a selectable alternative for sites that were already using it before 1.1.9. Its free, no-API-key tier only serves the endpoint over plain HTTP (not HTTPS) and is licensed for non-commercial use only.
  ip-api.com: [https://ip-api.com/](https://ip-api.com/) — [Terms of Service](https://ip-api.com/docs/legal) / [Privacy Policy](https://ip-api.com/docs/legal)

This lookup happens automatically as part of the plugin's core tracking feature and cannot be turned off without disabling geolocation entirely. Because visitor IP addresses leave your server either way, mention whichever provider you have selected in your site's own privacy policy. The "Anonymize IP" setting (Settings → Privacy) masks the stored IP address in your own database but does not change what is sent to the geolocation provider for the lookup itself.

== Features ==

Free:
* Recent visitors by IP, with country/region/city
* Live "online now" counter (5-minute window), auto-refreshing
* Browser, OS and device breakdown (pie charts)
* Search-engine totals + latest search keywords
* Top referring sites (paginated)
* Hourly "visits today" table
* 14-day traffic trend line chart
* Traffic by page title
* Summary stats: today / yesterday / 7d / 30d / 12m / total, new vs
  returning, bounce rate, average session duration
* World map of visitor locations (Leaflet + OpenStreetMap, no API key)
* Front-end shortcodes: [vtrs_online], [vtrs_stat type="..."]
* Bot filtering, IP exclusion list, admin self-exclusion
* Optional IP anonymization, configurable data retention + daily cleanup
* Optional consent-gating: hold off all tracking (and the tracking
  cookie) until the visitor has consented, auto-detected from
  Complianz or CookieYes, or your own consent tool via a filter
* Scheduled email reports — weekly (every Monday) or monthly (1st of
  the month) traffic summary sent to one or more recipients, with a
  "send a test now" button
* 404 tracking — which URLs visitors are hitting that don't exist, how
  often, and the page each broken link was followed from
* Outbound link and file-download click tracking, with a combined
  "most clicked" table

Pro (locked behind `vtrs_is_pro()` — wire up your own licensing/payments):
* Full country-level traffic tables (beyond the free top-5 preview)
* Top referring countries
* Full traffic-source breakdown (direct / search / social / referral)
* CSV export of raw visit data
* Unlimited history on every widget

== Installation ==

1. Upload the `visitor-traffic-stats` folder to `/wp-content/plugins/`.
2. Activate the plugin from the *Plugins* screen in WordPress.
3. Open *Traffic Stats* in the admin menu to view the dashboard.
4. Optional: visit *Traffic Stats → Settings* to exclude IPs, toggle bot
   filtering, anonymize IPs, or change data retention.

== Frequently Asked Questions ==

= Does this plugin need Google Analytics or a separate tracking account? =

No. Tracking runs entirely on your own server and database; there's no
external account or script to set up. The one external call the plugin
makes is the IP geolocation lookup described in "External services"
above.

= Does this slow down my site for visitors? =

The public-facing tracking and heartbeat endpoints are rate-limited per
IP, and the dashboard's heavier queries are served from a pre-aggregated
summary table rather than scanning raw visit history on every page
load — see "Notes for developers" below.

= Is visitor data sent anywhere other than the geolocation provider? =

No. All visit data is stored in your own database. See "External
services" above for exactly what is sent to the geolocation provider,
and how to avoid it.

= What happens to the data if I delete the plugin? =

By default, deleting the plugin removes its settings but leaves your
collected visit data in place, in case you reinstall it later. To also
drop the data tables on uninstall, define `VTRS_REMOVE_DATA_ON_UNINSTALL`
as `true` in `wp-config.php` before deleting — see "Notes for
developers" below.

= Can I use this with a cookie-consent plugin? =

Yes — turn on "Require cookie consent" under Settings → Privacy.
Consent from Complianz or CookieYes is detected automatically; any
other consent tool can be supported via the `vtrs_has_consent` filter.

== Notes for developers ==

* Geolocation defaults to the free freeipapi.com JSON endpoint (no
  key, HTTPS, rate limited) with ip-api.com selectable as a legacy
  alternative — see "External services" above — and caches each IP's
  result for 7 days via transients. Swap in a MaxMind GeoLite2 local
  database by adding a case to `VTRS_Geo::lookup()`. Because the
  visitor's IP is sent to whichever third-party endpoint is selected,
  mention it in your site's own privacy policy.
* When "Require cookie consent" is on, `vtrs_has_consent` lets you
  tell VTRS the answer yourself (return `true`/`false`; returning
  `null`, the default, falls through to the built-in Complianz/
  CookieYes detection):
  `add_filter( 'vtrs_has_consent', fn() => my_plugin_has_analytics_consent() );`
* `vtrs_is_pro()` is a filterable gate (`vtrs_is_pro`) with no opinion
  of its own about licensing — it returns `false` unless something
  hooks the filter and says otherwise. The **Visitor Traffic Stats
  Pro** add-on (separate plugin, sold/activated independently) is what
  actually hooks it: it owns license-key storage/validation and
  returns `true` once a site has a valid, active license. This plugin
  never needs to know how licensing works — it only asks "am I
  unlocked?" via the filter. Point a license-checking plugin at your
  own backend (a self-hosted REST endpoint, EDD Software Licensing,
  Freemius, etc.) and it will "just work" here.
* Two data-safety rules for anything gated behind `vtrs_is_pro()`:
  never `wp_send_json_success()` / never echo the *unlocked* rows into
  a page's HTML (even visually hidden) unless the current request is
  actually Pro — query with a free-tier row cap instead of fetching
  everything and hiding the extra rows with CSS.
* All visit data lives in `{prefix}vtrs_visits` and `{prefix}vtrs_online}`,
  created via `dbDelta()` on activation. `{prefix}vtrs_daily_stats` holds
  one pre-aggregated (visitors, visits) row per completed calendar day —
  see `VTRS_DB::aggregate_day()` / `catch_up_daily_stats()` — and is
  rebuildable at any time by truncating it and letting the daily cron
  (or `VTRS_DB::catch_up_daily_stats()`) repopulate it from raw history.
* 404 hits and outbound/download clicks share one `vtrs_events` table,
  keyed by `event_type` — a deliberately generic shape so the next event
  type is a new string rather than another schema migration. URLs are
  grouped on `url_hash` (an md5 of the URL) because the full URL is a
  TEXT column and can't be indexed or grouped on efficiently.
* Uninstalling the plugin removes its options; it only drops the data
  tables if you define `VTRS_REMOVE_DATA_ON_UNINSTALL` as `true` in
  `wp-config.php` first.

== Changelog ==

= 1.1.13 =
* New: 404 tracking. The dashboard now lists the URLs visitors are
  requesting that don't exist, how many times each was hit in the last
  30 days, and the site each broken link was followed from — so you can
  tell a typo in your own menu apart from a stale link elsewhere apart
  from a scanner probing for files. Includes a "Clear 404 log" button
  for after you've fixed things. Toggle under Settings → 404 tracking.
* New: outbound link and download click tracking. Clicks on links that
  leave your site, and on downloadable files (PDF, ZIP, DOCX, MP4 and
  friends — extendable via the `vtrs_download_extensions` filter), are
  recorded and shown in a "most clicked" table. Handled by the existing
  front-end script, so no extra asset is loaded, and clicks are sent via
  `navigator.sendBeacon` so they aren't lost to the navigation they
  trigger.
* Both features reuse the existing tracking gates — admin exclusion,
  consent gating, the IP exclusion list, bot filtering and rate limiting
  — so nothing is recorded for a visitor who isn't already being
  tracked, and both honour the configured data-retention window.

= 1.1.12 =
* New: scheduled email reports. Enable under Traffic Stats → Settings
  to have a summary (visitors, page views, top 5 pages, top 5
  countries) emailed automatically — weekly every Monday, or monthly
  on the 1st — to the site admin or any comma-separated list of
  addresses. Includes a "Send test report now" button so you can see
  exactly what it looks like before turning it on.

= 1.1.11 =
* The Go Pro page's "Get Visitor Traffic Stats Pro" link and the readme/
  plugin-header Plugin URI now point at the real product pages
  (ictworld24.com's product gallery) instead of the earlier placeholder
  path.
* Removed the static "illustrative" pricing grid from the Go Pro page —
  pricing is now managed on the product page itself (so it can't go
  stale inside the plugin) and the Go Pro page links there instead of
  duplicating numbers that could drift out of sync with what's actually
  charged.

= 1.1.10 =
* Fix: removed the permanent "50% OFF" badge and the fake struck-through
  "was $X" prices on the Upgrade-to-Pro banner and Go Pro pricing cards.
  1.1.9's changelog claimed this had already been fixed, but the badge
  on the dashboard banner (and the equivalent strikethrough prices on
  the Go Pro page) were never actually removed from the code — a
  discount shown at all times, with no real promotion behind it, is
  misleading regardless of where it appears. Actual prices are
  unchanged; only the fake "was more expensive" framing is gone.
* Compliance: Text Domain changed from `vtrs` to `visitor-traffic-stats`
  (matching the plugin's slug) across the plugin header and all 159
  translation calls, as required for translate.wordpress.org to pick
  up community translations once the plugin is hosted there.
* Compliance: added License / License URI headers to the plugin's main
  file (readme.txt already had them; the plugin header did not).
* Docs: bumped "Tested up to" to 7.0.

= 1.1.9 =
* Privacy/security: default geolocation provider switched from
  ip-api.com to freeipapi.com. The free/no-key tier of ip-api.com
  only serves its JSON endpoint over plain HTTP (visitor IPs were
  sent in cleartext) and is licensed for non-commercial sites only;
  freeipapi.com's free/no-key tier serves the same kind of lookup
  over HTTPS and permits commercial use. ip-api.com remains available
  as a selectable option under Settings → Geolocation provider for
  continuity — existing sites keep whatever provider they already had
  configured; only new installs (and sites that haven't set this
  option before) pick up the new default.
* Hardening: the Geolocation provider setting is now validated
  against an allow-list on save instead of storing whatever value was
  posted.
* Docs: added an explicit "External services" section to readme.txt
  disclosing both geolocation providers, and bumped "Tested up to".
* Marketing copy: the dashboard's "Upgrade to Pro" button no longer
  displays a permanent "— 50% off" claim (a discount shown at all
  times, with no actual promotion behind it, reads as misleading
  marketing rather than a real offer). If/when a real promotion is
  running, put its actual terms here instead of a static percentage.

= 1.1.8 =
* Minimum PHP version raised to 8.1 (from 7.4, which has been EOL
  since Nov 2022) — closes roadmap item #10. No behavior change; CI
  now tests 8.1-8.4 instead of 7.4-8.3.

= 1.1.7 =
* Fix: visits from iPhone/iPad were being recorded with OS "macOS"
  instead of "iOS" in the dashboard's OS breakdown. Cause: every iOS
  Safari user-agent string contains the literal substring "like Mac
  OS X" (e.g. "CPU iPhone OS 17_5 like Mac OS X"), and the OS
  detection checked for "Mac OS X" before checking for
  iPhone/iPad/iPod, so it matched first. The check order is now
  iPhone/iPad/iPod before Mac OS X; genuine desktop Safari/macOS
  visits are unaffected. Only affects how new visits are classified
  going forward — historical rows already stored as "macOS" are not
  retroactively reclassified.
* Dev: added a PHPUnit test suite (`tests/`) covering the plugin's
  pure logic — user-agent parsing, referrer/search-keyword parsing,
  bot detection, and CSV/formula-injection sanitization — plus a
  GitHub Actions workflow (`.github/workflows/ci.yml`) that lints
  every PHP file across PHP 7.4–8.3, runs the test suite, and runs an
  advisory (non-blocking) PHPCS pass against WordPress Coding
  Standards. No runtime behavior changes from this item; see
  `tests/` and the workflow file for details. It was this new test
  suite that caught the iOS/macOS bug above.

= 1.1.6 =
* Performance/privacy: Chart.js and Leaflet (+ its default marker
  icons) are now bundled locally under `assets/vendor/` instead of
  loaded from cdnjs.cloudflare.com. The dashboard no longer depends
  on a third-party CDN being reachable, and loading it no longer
  sends a request to a third party at all. Versions unchanged
  (Chart.js 4.4.4, Leaflet 1.9.4); their MIT licenses are included
  alongside the bundled files. The world-map tile imagery itself
  still comes from OpenStreetMap's tile servers, as before — map
  tiles aren't something a plugin can bundle.

= 1.1.5 =
* Performance/scale: added a `vtrs_daily_stats` summary table that
  pre-aggregates (visitors, visits) per completed calendar day via the
  daily cron, so dashboard range summaries (today/yesterday/7d/30d/12m/
  total) and the trend chart sum a handful of small rows instead of
  running a GROUP BY scan over the full raw table every load. Existing
  history is backfilled automatically in the background right after
  updating (no action needed). Falls back to the exact live query
  automatically for any range not yet fully covered by the summary
  table, and always computes "today" live — never a stale or
  approximate number, only a faster one once history is aggregated.
  Aggregated totals also survive data-retention pruning of the raw
  rows they were built from, so "all-time" stays accurate even on
  sites with a short retention window.

= 1.1.4 =
* Privacy: added an optional "Require cookie consent" setting. When
  enabled, no visit is recorded and no vtrs_sid cookie is set until
  a recognized consent signal says the visitor has opted into
  analytics/statistics cookies — closing the gap where tracking
  previously started on first pageload regardless of consent status.
  Consent from Complianz and CookieYes is detected automatically;
  other consent-management tools can be supported via the new
  vtrs_has_consent filter. Off by default, so existing behavior is
  unchanged unless a site owner opts in.

= 1.1.3 =
* Security/hardening: added per-IP rate limiting to the two
  unauthenticated public entry points (pageview tracking and the
  heartbeat AJAX action) — 90 pageviews/min and 60 heartbeats/min per
  IP. Blunts scripted flooding/DB-bloat abuse without affecting real
  visitors (well above normal browsing or the 20-second heartbeat
  interval). Requests over the limit are silently skipped (tracking)
  or return a 429 (heartbeat) rather than erroring the page.

= 1.1.2 =
* Security: GeoIP lookup now requests ip-api.com over HTTPS instead
  of HTTP, so the visitor's IP address is not sent to the third-party
  lookup provider in plaintext.

= 1.1.1 =
* Security: sanitize CSV export cells to prevent CSV/formula injection
  (a crafted referrer or page URL could otherwise execute as a
  formula when the export is opened in Excel/Sheets/LibreOffice).


= 1.1.0 =
* New: dashboard UI fully redesigned — refreshed color system, card-based
  layout, elevated shadows, trend indicators (▲/▼ vs. yesterday) on the
  summary cards, and a light/dark theme toggle (saved per browser).
* Security/Fix: free-tier country tables and traffic-source list are now
  limited to a real top-N *at the database query level* instead of
  fetching the full dataset and hiding it behind a CSS blur — the extra
  rows never reach the page on a non-Pro site.
* Fix: "Top Referring Countries" now reflects actual external-referral
  traffic (country of visitors who arrived via a non-search external
  link), instead of re-labelling the general country-traffic table.
* Enhancement: added `vtrs_country_table_limit` filter so the free-tier
  row cap is adjustable.
* Enhancement: official companion **Pro add-on** (separate plugin) with
  real license activation now available — see "Notes for developers".

= 1.0.1 =
* Fix: "Avg Session Duration" always showed 0 — the heartbeat script
  only loaded on pages using [vtrs_online], and even then never wrote
  elapsed time back to the visit row. Duration now accumulates for
  every visitor on every page.
* Fix: the online/heartbeat AJAX call was overwriting each session's
  page_url and country_code with blank values on every ping instead
  of preserving them.
* Security: visitor IP no longer trusts X-Forwarded-For / CF-Connecting-IP
  / X-Real-IP headers by default (anyone could spoof those to fake
  their recorded location on a site not actually behind a proxy/CDN).
  Added a "Trust proxy headers" setting for sites that are.
* Fix: CSV export's permission check passed an unsupported third
  argument to check_admin_referer(), which had no effect; access
  control is now capability-check-then-nonce-check, in that order.
* Fix: a country-traffic query built part of its SQL through one
  wpdb::prepare() call and then re-prepared the whole string — worked,
  but fragile; now built as a single prepare() with all arguments.
* Fix: referrer-vs-home-URL comparison could mistake an external
  "www." referrer for an internal one (or vice versa); both sides are
  now normalized before comparing.
* Hardening: moved template helper functions out of the dashboard view
  (declaring functions inside an included file can fatal with "cannot
  redeclare" if the view is ever rendered twice) into a proper
  VTRS_Admin_Helpers class.
* Hardening: added a composite (ip, visit_time) index to speed up the
  new-vs-returning-visitor query on larger sites; added an automatic
  schema-upgrade check on plugins_loaded so existing installs pick up
  index/column changes without needing to deactivate/reactivate.
* Hardening: added index.php stubs to every plugin folder.
* Enhancement: admin capability is now filterable via
  `vtrs_admin_capability` instead of being hardcoded to manage_options.
* Enhancement: 14-day trend chart now shows friendly date labels
  ("Sep 8") instead of raw ISO dates.

= 1.0.0 =
* Initial release.
