WP Visitors Tracker
A comprehensive, privacy-focused visitor tracking and analytics plugin for WordPress that provides detailed insights into your website traffic without compromising performance or user privacy.
by Avniloff Avraham · github.com/avniloff/wp-visitors-tracker · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/avniloff/wp-visitors-tracker/archive/refs/heads/main.zipWP Visitors Tracker — Developer Documentation
This README documents the key files, classes, functions, hooks, objects, and constants in the plugin. It’s organized per file so you can quickly find behavior and extension points.
- Minimum WP: 6.5
- Minimum PHP: 8.1
- Text domain:
wpvt
Contents
- wpvt.php
- autoload.php
- src/Core/PluginBootstrap.php
- src/Core/Activation.php
- src/Core/Deactivation.php
- src/Helpers/VisitorTypes.php
- src/Application/VisitProcessor.php
- src/Services/Detection/ClientIpResolver.php
- src/Services/Detection/UserAgentParser.php
- src/Services/Detection/VisitContextCollector.php
- src/Services/Sessions/SessionService.php
- src/Repository/VisitRepository.php
- src/Admin/Controller/AdminAjaxController.php
- src/Admin/Controller/ClientAjaxController.php
- src/Assets/pages/dashboard-page.php
- src/Assets/js/admin.js
- src/Assets/js/frontend-collector.js
- src/Assets/css/admin.css
Release Preparation (Minimal)
- Bump version in
wpvt.php(header +WPVT_VERSION). - Update
CHANGELOG.mdwith a new top section. - Ensure dev generator is disabled in production:
- Keep
WPVT_DEV_GENERATOR= false (default) andWP_DEBUGoff.
- Exclude non-distributable artifacts when packaging (optional for GitHub source):
tests/,docker-compose.yml,Dockerfile. - Tag release:
git tag v1.0.7 && git push origin v1.0.7
- Draft GitHub Release → paste changelog for that version.
- Smoke test on clean WP:
- Activate → visit front page → confirm a row in
wpvt_visits. - Send UA
curl/7.xto trigger hard ban. - Repeat soft UA (e.g.
MyBot crawler/1.0) twice to trigger soft ban.
Key Filters Exposed:
wpvt_ban_soft_repeats,wpvt_ban_soft_window_sec,wpvt_ban_default_ttl_minutes,wpvt_ban_hard_ttl_minuteswpvt_session_ttl,wpvt_session_fingerprint_recover_window
Dev Generator Gating:
Loaded only if WPVT_DEV_GENERATOR true OR WP_DEBUG true. Example to enable locally:
define('WPVT_DEV_GENERATOR', true);
wpvt.php
Main plugin bootstrap.
- Constants
WPVT_VERSION— current plugin version string.WPVT_PLUGIN_FILE,WPVT_PLUGIN_DIR,WPVT_PLUGIN_URL,WPVT_PLUGIN_BASENAME— paths/URLs for plugin.
- Behavior
- PHP version guard (>= 8.1): shows admin notice and deactivates the plugin if unmet.
- Requires
autoload.php. - Boots the plugin on
plugins_loadedviaWPVT\Core\PluginBootstrap::getInstance().
- Hooks
register_activation_hook(__FILE__, [Activation::class, 'activate']).register_deactivation_hook(__FILE__, [Deactivation::class, 'deactivate']).register_uninstall_hook(__FILE__, [Deactivation::class, 'uninstall']).add_action('plugins_loaded', ...).
autoload.php
Simple PSR‑4 autoload for the WPVT\ namespace, mapping to src/.
- Functions
spl_autoload_register(callable)— resolvesWPVT\\...tosrc/... .phpand requires if the file exists.
src/Core/PluginBootstrap.php
Primary plugin singleton. Wires hooks, admin UI, and front‑end assets.
- Class:
WPVT\Core\PluginBootstrap- Properties
private static ?PluginBootstrap $instance— singleton instance.private ?Application\VisitProcessor $orchestrator— lazy orchestration root.private Repository\VisitRepository $VisitRepositoryprivate Services\Detection\VisitContextCollector $VisitContextCollectorprivate Services\Sessions\SessionService $SessionService
- Methods
public static function getInstance(): PluginBootstrap— lazy singleton accessor.private function __construct()— builds core services and callsinit().private function init(): void— registers WP hooks; conditionally loads AJAX handlers; enqueues assets.public function trackFrontVisit(): void— processes the current request via orchestrator ontemplate_redirect(after canonical redirects).public function onShutdown(): void— cleanup expired sessions.public function enqueueFrontendScripts(): void— enqueuesfrontend-collector.jsand localizeswpvt_ajax(url, nonce, session_id).public function enqueueAdminScripts(string $hook): void— enqueues admin CSS/JS on plugin page; localizeswpvt_ajax(url, nonce).private function isTrackingEnabled(): bool— readswpvt_tracking_enabledoption (default true).private function getOrchestrator(): Application\VisitProcessor— lazy-creates orchestrator.public function addAdminMenu(): void— adds top‑level admin menu page and settings subpage.public function initAdminSettings(): void— registers settings actually used.
public function renderAdminPage(): void— requiressrc/Assets/pages/dashboard-page.php.public function renderSettingsPage(): void— requiressrc/Assets/pages/settings-page.php.private function __clone()— disabled cloning.public function __wakeup(): void— throws to prevent unserialize.
- Hooks Registered
parse_request(early bot blocking),template_redirect(priority 99) for tracking,shutdown,wp_enqueue_scripts,admin_menu,admin_init,admin_enqueue_scripts.
- Properties
src/Core/Activation.php
Activation and schema management.
- Class:
WPVT\Core\Activation- Methods
public static function activate(): void— creates tables/options and runs migrations.public static function migrateDatabase(): void— inspects tables withDESCRIBEand runsALTER TABLEfor structure alignment (enum types, indexes, column additions/removals, widths). Targets tables:wpvt_sessions,wpvt_visits,wpvt_visitors.- Table creators (use
dbDelta):private static function createSystemLogsTable($wpdb): voidprivate static function createVisitsTable($wpdb): voidprivate static function createSessionsTable($wpdb): voidprivate static function createVisitorsTable($wpdb): voidprivate static function createGeoCacheTable($wpdb): voidprivate static function createBansTable($wpdb): void
private static function createDefaultOptions(): void— addswpvt_tracking_enabled.
- Schema Notes
- All tables:
utf8mb4_unicode_ci, InnoDB, sensible indexes (session, url, timestamps).
- All tables:
- Methods
src/Core/Deactivation.php
Deactivation and uninstall cleanup.
- Class:
WPVT\Core\Deactivation- Methods
public static function deactivate(): void— deleteswpvt_tracking_enabled, flushes cache, logs deactivation time to optionwpvt_deactivated_at(and error_log whenWP_DEBUG).public static function uninstall(): void— deletes option(s), drops all plugin tables, removes uploaded files under/uploads/wpvt/, and flushes cache.private static function clearCache(): void—wp_cache_flush().private static function logDeactivation(): void— updates timestamp and logs in debug mode.private static function dropTables(): void—DROP TABLE IF EXISTSfor all plugin tables.private static function removeUploads(): void— removes uploads dir recursively.private static function removeDirectory(string $dir): bool— recursive delete helper.
- Methods
src/Core/VisitorTypes.php
Centralized visitor type constants and validation helpers.
- Class:
WPVT\Core\VisitorTypes- Constants:
HUMAN,BOT,DEFAULT = HUMAN. - Methods
public static function validate($visitorType): string— returns a valid type or default.public static function isValid($visitorType): bool— membership check.
- Constants:
src/Application/VisitProcessor.php
Atomic request handling and coordination of detection, session management, and logging.
- Class:
WPVT\Application\VisitProcessor- Constructor
__construct(Services\Detection\VisitContextCollector, Logger\VisitRepository, Services\Sessions\SessionService)— DI wiring.
- Methods
public function handleRequest(): bool— detects visit; if skippable returns true; otherwise starts DB transaction, gets/creates session, logs visit, commits; on exceptions rolls back and logs error.public function cleanup(): int— delegates toSessionService::cleanupExpiredSessions().
- Constructor
src/Services/Detection/ClientIpResolver.php
Resolves the best-guess client IP from headers.
- Class:
WPVT\Services\Detection\ClientIpResolver- Methods
public function getIpAddress(): string— checks headers in order (HTTP_X_FORWARDED_FOR,HTTP_X_REAL_IP,HTTP_CLIENT_IP,REMOTE_ADDR), takes first IP (first item if a list), prefers public IPs, falls back to any valid IP, defaults to127.0.0.1.
- Methods
src/Services/Detection/UserAgentParser.php
User Agent and browser metadata extraction.
- Class:
WPVT\Services\Detection\UserAgentParser- Methods
public function getUserAgent(): string— reads$_SERVER['HTTP_USER_AGENT'], trims to 500 chars.public function getScreenResolution(): ?string— readswpvt_screen_resolutioncookie; validates^\d{1,5}x\d{1,5}$.public function getBrowserLanguage(): ?string— parsesHTTP_ACCEPT_LANGUAGE, returns two‑letter code or null.public function detectVisitorType(): string— naive bot check by UA substrings (bot,crawler,spider,scraper).public function getBrowserDisplay(?string $userAgent = null, ?string $browserType = null): array— returns display name/class using explicitbrowserTypefirst or UA matching (Edge/Firefox/Opera/Safari/Chrome/Unknown).
- Methods
src/Services/Detection/VisitContextCollector.php
Aggregates detection logic and decides whether to track the request.
- Class:
WPVT\Services\Detection\VisitContextCollector- Properties: lazy
ClientIpResolverandUserAgentParser. - Methods
public function detectVisit(): ?array— returns a structured array withip_address,user_agent,url,referer,screen_resolution,browser_language,visitor_type,browser_type, or null if should not track.private function shouldTrackRequest(): bool— excludes admin (/wp-admin),admin-ajax.php, REST (/wp-json/), cron/login/xmlrpc, static assets by extension, and logged‑in admins.private function getRequestUrl(): string— fromREQUEST_URI, sanitized.private function getReferer(): string— fromHTTP_REFERER, validated URL or empty.private function detectBrowserType(): ?string— UA parser that prefers Edge, then Firefox/Opera/Safari, then Chrome, elseunknown.
- Properties: lazy
src/Services/Sessions/SessionService.php
Short‑lived session management tied to visits.
- Class:
WPVT\Services\Sessions\SessionService- Constants
private const SESSION_DURATION = 43200— seconds (12 hours by default; override viawpvt_session_ttl).
- Properties
private string $sessionsTable— resolved from$wpdb->prefix.
- Methods
public function getOrCreateSession(array $visitData): ?string— validates IP/UA; queries the latest active, unexpired session byip_address + user_agent; updates it or creates a new one; returns session_id.private function createNewSession(array $visitData): ?string— inserts new row; returns session_id.private function updateSession(string $sessionId, array $visitData): void— refresheslast_activity, extendsexpires_at, incrementspage_views.private function generateSessionId(): string—bin2hex(random_bytes(32)).public function getSessionData(string $sessionId): ?array— row fetch bysession_idwhen active.public function cleanupExpiredSessions(): int— setsis_active = 0for expired sessions; returns affected count.
- Constants
src/Repository/VisitRepository.php
Atomic visit logging with counter semantics.
- Class:
WPVT\Repository\VisitRepository- Methods
private function sanitizeTextField($value, string $fallback = ''): string— wrapper aroundsanitize_text_fieldwith fallback.public function logVisit(array $visitData, string $sessionId): bool— validates inputs; sanitizes; executesINSERT ... ON DUPLICATE KEY UPDATEintowpvt_visitskeyed by(session_id, url)- On insert: sets counts to 1 and timestamps to NOW.
- On duplicate: increments
visit_count, updateslast_visit, and conditionally updatesscreen_resolution,browser_language,browser_type.
- Methods
src/Admin/Controller/AdminAjaxController.php
Authenticated admin AJAX handlers (PSR-4 controller).
- Registers:
wp_ajax_wpvt_clear_visits(delegates toVisitRepository::truncateVisits())- Settings toggles and soft heuristics updates
- IP details via
InsightsService - Ban CRUD (IP, CIDR, UA) via
BansService
src/Admin/Controller/ClientAjaxController.php
Public (and authenticated) AJAX handlers for client‑side enrichments (PSR-4 controller).
- Registers:
wp_ajax[_nopriv]_wpvt_update_browser— updates latest visit'sbrowser_typeif previouslychromeviaVisitRepository.wp_ajax[_nopriv]_wpvt_set_screen_resolution— updates latest visit'sscreen_resolutionviaVisitRepository(no‑op if unchanged).
src/Assets/pages/dashboard-page.php
Admin dashboard page rendering a summarized recent activity table.
- Behavior
- Queries aggregated recent visits (grouped by
session_id, ip_address, user_agent), limited to 50 groups ordered by last activity. - Uses
UserAgentParser::getBrowserDisplay()for browser name/class. - Outputs a table with: ID, time, IP, browser, type (human/bot), visits total, pages visited, language, resolution.
- Includes a “Clear Table” button with id
clear-table.
- Queries aggregated recent visits (grouped by
src/Assets/js/admin.js
Admin page interactions (jQuery).
- Behavior
- Binds click to
#clear-tableand performs AJAX POST towpvt_clear_visitswithwpvt_ajax.nonce. - On success, clears table body and shows a transient message.
- Binds click to
src/Assets/js/frontend-collector.js
Universal front‑end collector for browser type (incl. Brave) and screen resolution.
- Globals/Interfaces
- Relies on localized
wpvt_ajaxobject:{ url, nonce }.
- Relies on localized
- Classes
BrowserDetector— UA‑based detection with async Brave checks (navigator.brave, Brave wallet, feature probes, ad blocking heuristic). Methods:detect(),detectByUserAgent(),isBrave(),checkBraveAPI(),checkAdBlocking().ScreenDataCollector— collects screen width/height (CSS pixels), stores in cookie/localStorage, sends via AJAX. Methods:collect(),sendViaAjax(resolution).DataSender— staticsendBrowserType(browserType)for posting Brave type updates.UniversalCollector— orchestrates init: collects screen data, detects browser, posts Brave type, sets up resize tracking. Methods:init(),setupResizeTracking().
- Lifecycle
documentReady()bootstrapsUniversalCollectorafter DOM is ready.
src/Assets/css/admin.css
Styling for the admin dashboard table and controls.
- Defines variables and layout for header, table, buttons; responsive tweaks for tablet/phone.
- Utility classes:
.bot-type,.human-type, column widths, hover states, and truncation behavior.
Global Hooks Summary
- Activation:
register_activation_hook→Activation::activate() - Deactivation:
register_deactivation_hook→Deactivation::deactivate() - Uninstall:
register_uninstall_hook→Deactivation::uninstall() - Runtime
plugins_loaded→ bootstrap main classtemplate_redirect(priority 99) → tracking entry point (after canonical redirects)parse_request→ optional early bot blockingshutdown→ session cleanupwp_enqueue_scripts→ enqueue front‑end collectoradmin_menu,admin_init,admin_enqueue_scripts→ admin UI- AJAX
wp_ajax_wpvt_clear_visitswp_ajax_wpvt_update_browser,wp_ajax_nopriv_wpvt_update_browserwp_ajax_wpvt_set_screen_resolution,wp_ajax_nopriv_wpvt_set_screen_resolution
Security & Data Notes (quick reference)
- Nonces:
wpvt_admin_nonce(admin actions),wpvt_ajax_nonce(public enrichment AJAX). - DB access:
$wpdb->prepare()used for dynamic SQL;dbDelta()for schema. - Sanitization:
sanitize_text_field,esc_url_rawon writes;esc_html/esc_attron output. - Sessions: short‑lived DB sessions keyed by
ip_address + user_agent; visit counters maintained per(session_id, url)unique pair.
Development Tips
- Text domain: wrap new UI strings with
__(),esc_html__(), etc., usingwpvt. - When adding new AJAX handlers, always
check_ajax_refererand scope updates to the current visitor/session. - For DB changes, prefer
dbDelta()and keep indexes aligned with query patterns.
Admin JS modules (no central init)
- There is no central
core/init.jsin admin. Feature scripts self-initialize on DOM ready. - Modules are idempotent and use namespaced events with an "off-before-on" pattern to avoid duplicate bindings.
- Scripts are enqueued conditionally on plugin admin pages.
Privacy & Data Collection
The plugin stores minimal technical analytics in its own tables:
Collected fields per visit/session:
- IP address (can be anonymized via filter)
- User-Agent (trimmed to 500 chars)
- URL (visited resource)
- Referer (if valid URL)
- Screen resolution (if JS collector sends it)
- Browser language (2-letter code)
- Browser type (edge / firefox / chrome / opera / safari / unknown / brave via JS)
- Visitor type (human|bot — naive UA heuristic)
Not collected:
- POST bodies
- Form submissions
- Cookies besides the session id (
wpvt_sid) and optional screen resolution helper cookie - Logged-in admin navigation (skipped)
No automatic deletion/retention policy is enforced (for forensic transparency). Admin can manually purge.
Anonymizing IP Addresses
Enable a filter to obfuscate stored IPs (example: zero last octet for IPv4, collapse IPv6):
// In theme's functions.php or a small mu-plugin
add_filter('wpvt_anonymize_ip', '__return_true');
add_filter('wpvt_resolved_client_ip', function($ip){
if (!apply_filters('wpvt_anonymize_ip', false)) return $ip;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$parts = explode('.', $ip);
$ip = $parts[0] . '.' . $parts[1] . '.' . $parts[2] . '.0';
} elseif (strpos($ip, ':') !== false) {
$blocks = explode(':', $ip);
$ip = implode(':', array_slice($blocks, 0, 3)) . '::';
}
return $ip;
}, 20);
Alternative irreversible hash (keeps uniqueness, drops reversibility):
add_filter('wpvt_resolved_client_ip', function($ip){
if (!apply_filters('wpvt_anonymize_ip', false)) return $ip;
return hash('sha256', $ip . NONCE_SALT);
}, 30);
Adjust Log Verbosity
Default production level is info. Reduce noise further:
add_filter('wpvt_log_min_level', fn()=> 'warning');
Temporarily enable deep diagnostics:
add_filter('wpvt_log_min_level', fn()=> 'debug');
Purging Old Logs (Programmatic)
You can run a manual purge (e.g. via WP-CLI command or custom cron):
global $wpdb; $days = 30;
$table = $wpdb->prefix . 'wpvt_system_logs';
$wpdb->query($wpdb->prepare("DELETE FROM {$table} WHERE created_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", $days));
Preserve Data on Uninstall
By default uninstall drops all plugin tables. To keep data:
// wp-config.php or a small mu-plugin
define('WPVT_PRESERVE_DATA', true);
// OR set the option manually (e.g. via wp shell / db):
update_option('wpvt_preserve_data_on_uninstall', true);
// OR use a filter:
add_filter('wpvt_preserve_data_on_uninstall', '__return_true');
When preservation is enabled, uninstall exits early and leaves tables & options intact.
License
Licensed under GPL-2.0-or-later. See LICENSE file.