Search by

mediasoep / maintenance-reporter

MediaSoep

Reports a WordPress site's environment and maintenance events to SOILboard.

Package info

gitlab.com/mediasoep/maintenance-reporter

Issues

pkg:composer/mediasoep/maintenance-reporter

Statistics

Installs: 56

Dependents: 0

Suggesters: 0

Stars: 0

v1.5.4 2026-11-10 16:59 UTC

This package is auto-updated.

Last update: 2026-11-10 15:59:51 UTC


README

Reports a WordPress site's environment and maintenance events to SOILboard: the WordPress, PHP and database versions, the active theme, every installed plugin with its latest known version, site health, and events such as plugin and core updates, activations, theme switches, failed updates, stalled cron and PHP fatals. It also activates and checks the site's SOILboard licence.

It is the reporter inside SOIL and inside the standalone soilboard plugin for sites without SOIL. It uses WordPress APIs only: no framework, no runtime dependencies.

Requirements

  • PHP 8.1 or newer
  • WordPress 6.2 or newer
  • A SOILboard licence key registered for the site's url

Installation

composer require mediasoep/maintenance-reporter

Inside a WordPress plugin, prefix the package (for example with Strauss) so two plugins shipping different versions of it never collide on one install.

Usage

Boot it once from your plugin, as early as plugins_loaded:

use MediaSoep\MaintenanceReporter\Agent;
use MediaSoep\MaintenanceReporter\Config;

$agent = Agent::boot(new Config(
    optionPrefix: 'myplugin',
    agentName: 'myplugin',
    agentVersion: '1.0.0',
    dashboardUrl: 'https://www.soilboard.nl',
    extraSnapshotFields: static fn (): array => [],
    enabled: static fn (): bool => wp_get_environment_type() === 'production',
    pluginFile: __FILE__,
));

$result = $agent->license()->activate('XXXXX-XXXXX-XXXXX-XXXXX-XXXXX');
// ['status' => 'valid', …], or 'missing', 'invalid', 'expired', 'revoked', 'not_license_site', 'error'

$agent->license()->deactivate();
// ['status' => 'deactivated'] clears the key; 'failed' and 'error' keep it

From then on the agent records events as they happen and sends them, with a snapshot of the site whenever it changed, to {dashboardUrl}/api/v1/maintenance/events. A flush runs a minute after an event, every hour from cron, and at the end of the first request a new host version runs.

PartWhat it does
Agent::boot(Config)Builds every collaborator and hooks it into WordPress, also when booted after init.
$agent->license()activate(), check(), deactivate(), key(), information(), hasValidLicense(), downloadQueryArgs() for an update checker.
$agent->recorder()record(string $type, array $data) for events of your own.
$agent->reporter()flush(bool $force = false) to send now.
Agent::clearCron(Config)For your plugin's deactivation hook.

Configuration

ParameterMeaning
optionPrefixPrefix of everything the package stores: options, transients, cron events and hooks all start with {optionPrefix}_, so an uninstaller can delete by prefix.
agentName, agentVersionReported to SOILboard as agent: {name, version} and with licence activations (at most 32 characters each).
dashboardUrlSOILboard's base url.
extraSnapshotFieldsTop-level fields your plugin adds to the snapshot. They are hashed with it, but never override a field of the package's own.
enabledWhether to report at all. Return false on staging and local copies, so they never report as the live site; such a copy does not run the daily licence check either.
pluginFileYour main plugin file.
checkIntervalMinutesMinimum minutes between two snapshot collections (default 45).
logfn (string $level, string $message, array $context); by default messages go to error_log() while WP_DEBUG is on.
registerLicenseHooksWhether the package hooks the daily licence check itself (default true).
fallbackLicensePrefixClosure answering the option prefix of another plugin's licence in the same shape (soil for SOIL before 5.15), or null. While the host has no key of its own, LicenseClient uses that key and that licence's status, and its checks write the status there, so both plugins show one status whoever checked last; borrowsLicense() says so. The other licence's key is never written or deactivated, and a key the host fails to activate leaves that status alone. Hosts without a fallback (SOIL itself) are unaffected.

The remaining parameters (endpoint, maxEvents, chunkSize, maxBodyBytes, flushDelay, invalidWarningThreshold, bufferLockTtl, bufferLockWait, requestTimeout) tune delivery and have defaults that suit any site.

Multisite

The licence is network wide: only the main site activates, checks or deactivates it, and a subsite asking gets not_license_site without a request. Every site reports its own snapshot and events under its own blog_id.

Updates from SOILboard

SOILboard can ask a site to run updates (a maintenance run). The site pulls; SOILboard never sends a command:

  1. SOILboard wakes the site with a bare POST to its home url (the snapshot's updates.wake_url) and the header X-SOILboard-Wake: 1. The package answers 202 at plugins_loaded and closes the connection. It is no REST route or admin-ajax call, so SOIL's REST lock and Hide My WP leave it alone; a firewall must let the header through. Anyone may send it: it only makes the site ask.
  2. At wp_loaded the main site asks {dashboardUrl}/api/v1/maintenance/update-requests/next with its licence, and runs what it gets in the same request. The request runs as cron (DOING_CRON) from the wake on, as WordPress' own automatic updates do, so premium plugins that hook their updater in only in wp-admin or cron offer their updates here too. It never asks without a wake: a site SOILboard cannot wake fails at once and visibly, instead of updating at some moment nobody watches.
  3. WordPress' own automatic updater runs, with filters at PHP_INT_MAX that allow exactly the requested items for this run only: they win over the site's own auto-update choices and over a plugin that turns the updater off, but not over DISALLOW_FILE_MODS or AUTOMATIC_UPDATER_DISABLED. Only updates WordPress already offers can run; a request names items, never a package or url.
  4. The site reports running, then done, failed or refused with a result per item to …/update-requests/{id}, with the one-time token that came with the request, so only the site that fetched it can say how it went. Then it sends its events (actor soilboard) with a fresh snapshot.

The snapshot's updates field says whether the site can update itself and which core and theme updates WordPress offers it.

Commands from SOILboard

The answer to the fetch after a wake can also carry commands: [{id, name, payload}], with or without an update request. They run first, in order; each one that ran is reported to …/maintenance/site-commands/done with its id, and SOILboard hands out one it never heard back about again on a later fetch. A name the site does not know is skipped.

CommandWhat it does
reportSends the full report now, its snapshot included even when nothing changed. Kept until a snapshot is sent, so a flush that fails leaves the next one to send it.

A host or any other plugin adds its own command on the action soilboard/maintenance_reporter/commands, the same on every host whatever its prefix, which is passed the SiteCommands:

add_action('soilboard/maintenance_reporter/commands', function (MediaSoep\MaintenanceReporter\SiteCommands $commands): void {
    $commands->register('my-plugin/clear-cache', function (array $payload): void {
        // …
    });
});

Prefix your own names, so they never meet one the package adds later.

Failure behaviour

Reporting never throws into WordPress. A failed recording, flush or logger is caught and logged; an event that cannot be buffered is dropped and counted, and the count is reported. An "invalid" or "missing" answer from SOILboard backs off for a day instead of retrying every hour.

Development

The tests run against a real WordPress, installed into a database whose name must contain test (maintenance_reporter_tests by default, see tests/database.php). Point WP_CORE_DIR at any WordPress install for the core files.

composer install
composer test             # single site
composer test:multisite   # network
composer analyse          # PHPStan, PHP 8.1 rules

License

GPL-2.0-or-later.