Shibisty Sharing Activity Detector logs requests (page views, REST/AJAX calls, admin-area hits, and optional heartbeat pings) into a dedicated database table, then groups them into sessions per user/device. From that it computes, for each user:
Total − Range. A value greater than zero means two or more devices were active for the same user at the same time, which is the core signal this plugin is built to detect (e.g. a shared account being used from two locations at once).Session boundaries are computed with a simple gap rule: if two consecutive requests from the same user/device are ≤ 5 minutes apart, they belong to the same session and the time between them is counted; a gap of more than 5 minutes is treated as the user having left, and that gap is not counted toward active time.
All screens live under Suspicious Activity in the admin menu (manage_options capability required):
wp-suspicious-activity) — list of users with a suspicious-activity timeline, sortable/filterable.wp-sad-request-logs) — full paginated log of every recorded request, with filters and sortable columns.wp-sad-settings) — logging rules and heartbeat.wp-sad-log-view, hidden) — detail view of a single log entry, linked from the Request Logs list.wp-sad-activity-view, hidden) — detail view of a single user’s activity/timeline, linked from the Activity list.Filter selections on the Activity and Request Logs screens are remembered in the browser (localStorage) and restored on your next visit; the Reset button always clears them and returns to the default view. Wherever a user is identified from a session, their name links directly to their WordPress profile/edit-user page.
Found under Suspicious Activity Settings:
from–to). Windows that cross midnight (e.g. 22:00–06:00) are supported./wp-json/wp-sad/v1/heartbeat) at that interval for as long as the browser tab is open and visible, so active time is counted accurately even between page loads, without relying on admin-ajax.php.The plugin’s source strings are in English (shibisty-sharing-activity-detector text domain, .pot template included in /languages) and hand-written translations already exist for 30 additional languages — Ukrainian, Russian, German, French, Spanish, Italian, Portuguese (Brazil), Portuguese (Portugal), Polish, Dutch, Romanian, Czech, Hungarian, Bulgarian, Greek, Turkish, Swedish, Finnish, Lithuanian, Croatian, Serbian, Georgian, Azerbaijani, Kazakh, Belarusian, Arabic, Hebrew, Hindi, Chinese (Simplified), and Japanese. Like every WordPress.org-hosted plugin, translations are delivered through translate.wordpress.org and applied automatically based on each admin’s own profile language — there’s no plugin-specific language setting.
The plugin is split into logic and presentation layers rather than one monolithic file:
`
shibisty-sharing-activity-detector/
├── shibisty-sharing-activity-detector.php # Bootstrap: constants, requires, activation
├── includes/
│ ├── class-plugin.php # Orchestrator: hooks, wiring
│ ├── class-db.php # Table name/schema, install + upgrade (dbDelta), checked on every init
│ ├── class-settings.php # Settings storage and sanitization
│ ├── class-request-logger.php # Classifies + gates + records each request
│ ├── class-heartbeat.php # REST heartbeat route + front-end pinger
│ ├── class-session-analyzer.php # Pure session/timeline/risk calculation logic
│ ├── class-view-helpers.php / class-query-helpers.php
│ ├── class-admin-menu.php # Menu registration, asset enqueueing
│ └── admin/ # Page controllers (Activity, Request Logs, Log View, Activity View, Settings)
├── views/ # Plain PHP templates — no business logic or queries
├── assets/ # css/, js/ (timeline UI, filter persistence, heartbeat pinger)
└── languages/ # .pot template (translations delivered via translate.wordpress.org)
`
Request records carry a request_type (page, api, admin, or heartbeat), set at write time, which the session analyzer uses to decide what counts as a genuine presence signal versus administrative/system noise.