=== MySentry Monitor ===
Contributors: northstariw
Tags: monitoring, error-tracking, uptime, cron, analytics
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.7.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Reports PHP fatals, JavaScript errors, failed emails, a WP-Cron heartbeat and a read-only version inventory to your MySentry panel.

== Description ==

MySentry Monitor connects a WordPress site to [MySentry](https://www.mysentry.co.uk/), a website-monitoring service for small businesses and the freelancers and agencies who look after their sites. The plugin is the reporting side only: it observes, it never changes anything on your site, and it does nothing at all until you paste in a reporting URL from your MySentry panel.

**What it reports**

* **PHP fatal errors and uncaught exceptions** — the white screen of death — caught by a shutdown handler, with the request URL and PHP version. Optionally also PHP warnings.
* **JavaScript errors** in your visitors' browsers — including errors thrown while the page is still loading — capped at 10 per page view and de-duplicated.
* **Failed emails** — every failed `wp_mail()` send (contact forms, WooCommerce order emails, password resets).
* **WP-Cron heartbeat** — a pulse every hour (or every 12 hours, or daily), so a site whose scheduler has died is reported within your threshold.
* **Version inventory (read-only, since 1.5)** — core, plugin and theme versions, activations and available updates, sent once a day and after every update, plugin (de)activation or theme switch. Your MySentry panel shows how many days each update has been waiting.
* **Optional cookieless analytics** — off by default; one toggle enables a privacy-first page-view beacon (no cookies, no IP addresses stored).

**Designed not to slow your site**

Error reports wait until the end of the request. On PHP-FPM and LiteSpeed hosting the page is handed to the visitor first; elsewhere the sends are capped at five events and a few seconds per request. An identical error is reported at most once per 15 minutes. The heartbeat and the inventory are sent from WP-Cron, never during a visitor's request. Nothing runs until a reporting URL is configured.

**Tells you what actually happened**

The settings screen shows MySentry's last answer for error reports, the heartbeat and the inventory, and the **Send test** button checks both tokens and every stage separately. A refused heartbeat cannot hide behind an accepted test event.

**Staging copies stay quiet**

The plugin remembers the address it was set up on. A staging clone or a local copy of the site sends nothing, so it cannot keep a dead production cron looking alive or flip the production inventory back and forth. After a real domain move, press Save once.

**Read-only by design**

The plugin has no write path: it cannot install, update, delete or modify anything on your site, and the MySentry service is granted no write capability by it. It reads the update information WordPress itself already maintains.

== External services ==

This plugin is the reporting side of MySentry, a website-monitoring service run by the plugin's author. It connects to that one service only, and makes no external request at all until the site owner pastes a reporting URL from their own MySentry account into Settings → MySentry.

**Service:** MySentry, at www.mysentry.co.uk (hosted in the EU, Frankfurt). Terms of service: https://www.mysentry.co.uk/terms/ — Privacy policy: https://www.mysentry.co.uk/privacy/ — Security: https://www.mysentry.co.uk/security/

What is sent, when, and why — from the WordPress server, to the address in the reporting URL:

* **Error reports** (`/api/ingest/error`) — when a PHP fatal error or uncaught exception happens, when a PHP warning happens (only if the level is set to "all"), and when `wp_mail()` fails. Sent: the error message, a stack trace without argument values, the page address without its query string, the PHP and WordPress versions and the request method. Why: so the site owner is alerted to the error.
* **Heartbeat** (`/api/ingest/heartbeat`) — on the WP-Cron schedule the owner chooses (hourly, every 12 hours or daily), if enabled. Sent: a monitor name, and optionally the schedule or silence threshold. Why: so the owner is alerted when the site's scheduler stops.
* **Version inventory** (`/api/ingest/wp-inventory`) — once a day and about 90 seconds after an update, plugin (de)activation or theme switch, if enabled. Sent: WordPress, PHP, plugin and theme names and versions, which are active, which updates are available, the site locale and whether HTTPS, debug mode and multisite are on. Why: so the owner sees waiting updates and a history of changes.
* **Token check** (`/api/ingest/whoami`) — when settings are saved, when "Send test" is pressed, once a day, and shortly after the plugin is updated. Sent: the project name from the reporting URL and the site's address. Why: to confirm which kind of token each URL holds, so a server token is never printed into pages.

What is sent from visitors' browsers — only if a page URL is configured and the feature is enabled:

* **JavaScript errors** (to the page reporting URL) — when a script error happens in a visitor's browser. Sent: the error message, stack, the page address without its query string, and the browser's user agent. Enabled by the "JavaScript errors" setting.
* **Analytics** (`/a.js` loaded from www.mysentry.co.uk, beacons to the page reporting URL) — off by default. Sent on each page view: the page path, the referring domain, time on page and page-speed measurements (LCP, CLS, INP); no cookies are set and no IP address is stored. Logged-in editors and administrators are not counted.

Email addresses and phone numbers in error messages are masked by the service before storage.

== Installation ==

1. In your MySentry panel, add the website. Copy its **server URL** and its **page URL** (each shown once; only a hash is stored server-side).
2. Upload the plugin (Plugins → Add New → Upload) and activate it.
3. Open **Settings → MySentry**, paste the server URL into *Server URL* and the page URL into *Page URL*, save. The plugin asks MySentry which kind of token each one is and refuses a server token in the page field, because that field is printed into your pages.
4. For browser errors and analytics: add your site's address to the website's allowed origins in the panel.
5. Press **Send test** — every line should be green. Each line shows what MySentry answered.

Requires a MySentry account (free trial, paid plans from £1.99/month per site).

== Frequently Asked Questions ==

= Does the plugin slow my site down? =
Error reports are sent at the end of the request; on PHP-FPM and LiteSpeed hosting the visitor already has the page by then. Identical errors are sent at most once per 15 minutes and at most five per request. The heartbeat and inventory run in WP-Cron. On fully cached pages PHP does not run at all — JavaScript errors and analytics still report from the browser.

= Why two reporting URLs? =
The page URL is printed into your pages, so anyone can read it; it can only report browser errors and page views. The server URL stays inside WordPress and is the only one that can confirm the heartbeat — a visitor holding it could make a dead scheduler look alive.

= I use a staging site. Will it send reports? =
No. The plugin remembers the address it was set up on and stays quiet on any other address. If the site has really moved, open Settings → MySentry and press Save.

= Can the plugin or the service change my site? =
No. The plugin is read-only by design: it reports state and has no code path that installs, updates or modifies anything. Updates you run yourself in wp-admin are detected and verified by the service — never performed by it.

= Does it work with WooCommerce and page builders? =
Yes, with any theme and builder. WooCommerce order emails go through `wp_mail()`, so a broken mail provider surfaces as a failed-email alert.

= Is the analytics feature GDPR-friendly? =
It is off by default. When enabled it uses no cookies, stores no IP addresses and needs no consent banner — uniqueness is a daily-rotating server-side hash.

= What happens if MySentry is unreachable? =
The site serves normally. The settings screen shows that the last report could not reach MySentry, and the gap shows in your panel's heartbeat history.

= What is removed when I delete the plugin? =
Everything it stored: settings (including both reporting URLs), check results, delivery status, scheduled events and de-duplication markers. Deactivating keeps the settings.

== Screenshots ==

1. Plugin settings: reporting URL, error levels, heartbeat and inventory toggles.
2. MySentry panel — project dashboard with uptime, errors and cron tiles.
3. MySentry panel — WordPress tab: version inventory with "update available for N days".
4. MySentry panel — Changes feed with before/after evidence.

== Changelog ==

= 1.7.3 =
* Security: the plugin sends data to, and loads its analytics script from, MySentry's own hosts only (www.mysentry.co.uk, mysentry.co.uk). Before, anyone allowed to change its settings could point the page URL at another server and have that server's script loaded on every page. A self-hosted MySentry can be allowed with the MYSENTRY_MONITOR_HOSTS constant in wp-config.php.
* Reported page addresses use the site's own address instead of the request's Host header.

= 1.7.2 =
* Security: error text is cleaned before it leaves the site — the password in a URL (user:password@) and the values in a URL's query string are replaced with ***, so a failed request's API key never reaches MySentry. Server paths above the WordPress root (wp-config.php one level up, vendor directories, /home/<account>/) are shortened too.
* Repeats of an error that differ only in numbers are sent once, not once per request.

= 1.7.1 =
* readme: the External services section lists every request the plugin makes — endpoint, when, what is sent and why. No code changes.

= 1.7.0 =
* Settings in English and ready for translation.
* The plugin asks MySentry which kind of token each URL holds. A server token in the page field is refused on save, and one saved by an earlier version stops being printed within a minute of updating.
* A mistyped URL no longer wipes the working one (it used to switch all monitoring off).
* Send test checks both tokens and shows MySentry's answer for the test event, the heartbeat and the inventory separately; before, a refused heartbeat and inventory showed as success. The settings screen keeps the last answer for each.
* Error reports are sent at the end of the request, after the page is handed over on PHP-FPM and LiteSpeed. Before, each report held the page for up to 2 seconds.
* Fatal errors: the stack trace goes into the stack field and server paths are shortened; a crash from many small allocations is reported again (WordPress's own error page used to run out of memory first).
* Warnings silenced with @ are no longer reported, and another plugin's error handler keeps working.
* JavaScript errors thrown while the page loads are caught (the script now sits in the head); "Script error." and browser-extension noise are ignored; no CORS preflight per error.
* The heartbeat tells MySentry its schedule when WP-Cron runs from a system cron.
* A theme with an empty name no longer gets the whole inventory refused.
* Staging copies and other addresses stay quiet until settings are saved there.
* Analytics no longer counts logged-in editors and administrators.
* Deleting the plugin removes everything it stored.
* Tokens are never sent after a redirect, and the reporting URL must name a plain host.
* Error reports carry the page address without its query string, and stack frames without argument values.
* A fatal error is sent even after a burst of warnings, and a warning storm no longer writes to the database.
* The page URL is printed only after MySentry has confirmed it is a page token.
* An inventory change made within 5 minutes of the previous report is sent 5 minutes later instead of being lost.

= 1.6.0 =
* Two reporting URLs instead of one. The plugin used to print the same URL it
  uses for heartbeats and inventory into the footer of every page, so anyone
  reading the page source held a credential that could confirm the heartbeat of
  a cron that had already died — leaving the monitor showing health — and could
  raise the silence threshold so real silence never raised an alarm. The server
  URL now stays inside WordPress, and a separate page URL, which can do nothing
  but report browser errors and page views, is the only one written into HTML.
* Browser errors and the analytics beacon are not printed at all until a page
  URL is set: a silent fall back to the server URL would look exactly like the
  protection working. An admin notice says what is missing.

= 1.5.1 =
* Directory-ready readme (external-services disclosure, FAQ, screenshots). No functional changes.

= 1.5.0 =
* Read-only version inventory: core, plugins and themes with versions, activity and available updates, reported daily and after every update, plugin (de)activation or theme switch. Shown in the panel's WordPress tab with update ageing ("available for N days"). The plugin remains strictly read-only.

= 1.4.0 =
* Optional cookieless analytics beacon (page views, country, time on page) — off by default.

= 1.3.0 =
* Configurable heartbeat frequency and optional silence-threshold override sent with each pulse.

= 1.2.0 =
* Events carry level (fatal/error/warning/info/debug), request URL and metadata (PHP version, method; browser user agent for JS errors).

= 1.0.0 =
* PHP fatals, JavaScript errors, failed wp_mail() and WP-Cron heartbeat.

== Upgrade Notice ==

= 1.7.2 =
Security: API keys and passwords inside URLs in error text are masked before anything is sent.

= 1.7.0 =
Checks both reporting URLs with MySentry, stops printing a server token placed in the page field, and sends reports after the page is delivered. Existing error groups get new fingerprints once, because messages are now in English with shortened paths.

= 1.6.0 =
Security: add a separate page URL (MySentry → Settings → "New page token"). Until then browser errors and analytics stay off: the server URL is no longer printed into pages.

= 1.5.1 =
Readme and directory-listing preparation only; no functional changes.
