mkr / smartdesk-license-client
License and policy Client-SDK for licensing and updates.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:wordpress-plugin
Requires
- php: >=8.1
- smartdesk/debug-logger: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.6.20
- wp-phpunit/wp-phpunit: ^6.0
- yoast/phpunit-polyfills: ^2.0
README
License & policy Client-SDK for licensing, limits and plugin updates.
Provides a tiny REST client, admin settings, secure secret storage, and an optional plugin updater.
- PHP 8.1+
- WordPress (single site & multisite supported)
- Composer package: mkr/smartdesk-license-client
Features
- License verification with 12h caching (forceable)
- Limits & plan mapping (plan, limits[*], expires_at)
- Secure secret storage: encrypted at rest, admin UI never echoes secrets
- Safe logging via smartdesk/debug-logger (PII/keys redacted)
- Multisite-aware: options/secrets isolated per blog; per-blog home_url() in payload
- Optional updater: integrates with WP’s update API (gated by license validity)
- Well-tested: PHPUnit suites for single site & multisite
Requirements
- PHP >= 8.1
- WordPress (recent version)
- Composer
Installation
1. Install via Composer
composer require mkr/smartdesk-license-client
If used as a WordPress plugin, keep this repository as a plugin folder and run composer install.
2. Activate the plugin
- Upload/checkout the plugin into
wp-content/plugins/SmartDesk-LicenseClient/
- Activate in Plugins → SmartDesk License Manager Client
3. Configure API endpoints (production)
By default the endpoints are defined in the plugin bootstrap:
define('SDLC_API_BASE', 'https://server.local/'); define('SDLC_API_VERIFY', SDLC_API_BASE . '/wp-json/smartdesk/v1/license/verify'); define('SDLC_API_PING', SDLC_API_BASE . '/wp-json/smartdesk/v1/license/ping'); define('SDLC_API_UPDATE', SDLC_API_BASE . '/wp-json/smartdesk/v1/update/plugin');
For production, set your real base/paths.
Best practice is to ship these via your build or a small MU-plugin that defines the constants before this plugin loads.
Admin UI
Settings → SmartDesk License
- Enter the license key (never echoed back; stored encrypted).
- “Verify now” triggers a forced verification (bypasses the 12h cache).
- A status box shows the current verify result.
Public API (what you’ll use from other plugins/themes)
<?php use SmartDesk\LicenseClient\Api; // Gate features behind a license if (!Api::is_valid('smartdesk-core')) { add_action('admin_notices', function () { echo '<div class="notice notice-error"><p>License invalid or expired.</p></div>'; }); return; } // Read plan/limits $limits = Api::get_limits('smartdesk-core'); // Example: enforce seats $maxMembers = (int)($limits['members.max'] ?? 0);
Low-level client (if you need it)
<?php use SmartDesk\LicenseClient\Client; // Verify with cache (12h TTL) $res = Client::verify('smartdesk-core'); // Force verification (bypass cache) $res = Client::verify('smartdesk-core', true);
Caching policy
✅ Transport errors, 5xx and 429
are cached (avoid storms).
❌ 4xx (client errors) are not cached.
TTL: 12h (configurable in code).
Secure secret storage
Secrets are stored via Helpers::set_secret/get_secret
using HKDF-derived keys:
- Uses WP salts when available; falls back to a generated, persisted master key.
- Transparently migrates legacy plaintext values to encrypted form on first read.
- Admin inputs never echo stored secret values.
<?php use SmartDesk\LicenseClient\Helpers; // Store (encrypted) Helpers::set_secret('license_key', 'XXXX-XXXX-XXXX'); // Read (plaintext) $key = Helpers::get_secret('license_key', '');
Optional: Updater
Enable the updater to feed WordPress’ updater UI based on the license:
<?php // In plugin bootstrap \SmartDesk\LicenseClient\Updater::boot();
Server contract (SDLC_API_UPDATE
GET) should return e.g.:
{
"new_version": "1.2.3",
"homepage": "https://your-plugin-homepage",
"download_url": "https://.../your-plugin-1.2.3.zip"
}
The updater will:
- Verify
smartdesk-core
(or your chosen product slug) is valid - Inject an update object if
new_version > SDLC_VERSION
Logging (SmartDesk DebugLogger)
Only active when WP_DEBUG === true
. Secrets/keys are automatically redacted.
<?php use SmartDesk\Utils\DebugLogger; use SmartDesk\Utils\Support\LogLevel; add_action('plugins_loaded', function () { if (!defined('WP_DEBUG') || WP_DEBUG !== true) return; $dir = WP_CONTENT_DIR . '/uploads/smartdesk-logs'; DebugLogger::setWriter(DebugLogger::makeRotatingWriter($dir, 'sdlc.log', 2_000_000, 4)); DebugLogger::setMinLevel(LogLevel::INFO); });
Multisite behavior
- Per-blog isolation: license key/secret/options are stored per site (no cross-bleed).
Client::verify()
sends the current blog’shome_url()
in the payload.- A dedicated MS test suite ensures isolation and correct payload.
Testing
This project includes two PHPUnit suites:
- Single-site:
composer test
- Multisite:
composer test-ms
Prerequisites
- PHP 8.1+
- Composer
- A MySQL/MariaDB instance for tests (LocalWP works great)
Database config is supplied via phpunit.xml.dist
/ phpunit.multisite.xml
:
<env name="WP_DB_HOST" value="127.0.0.1:10016"/> <env name="WP_DB_NAME" value="wp_tests"/> <env name="WP_DB_USER" value="root"/> <env name="WP_DB_PASSWORD" value="root"/>
Create the DB once:
CREATE DATABASE IF NOT EXISTS wp_tests
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
WordPress core for tests
Point WP_PHPUNIT__WP_DIR
to your local WordPress core (downloaded once), e.g.:
<env name="WP_PHPUNIT__WP_DIR" value="C:\wptmp\wordpress\"/>
Run tests
composer install
composer test # single-site
composer test-ms # multisite
We pin PHPUnit 9.6 for compatibility with wp-phpunit/wp-phpunit.
Integration example (SmartDesk plugin)
A typical SmartDesk add-on/plugin can:
1. Require the client:
composer require mkr/smartdesk-license-client
2. Verify license during plugins_loaded
or before privileged actions:
<?php use SmartDesk\LicenseClient\Api; add_action('admin_init', function () { if (!Api::is_valid('smartdesk-core')) { add_action('admin_notices', function () { echo '<div class="notice notice-error"><p>SmartDesk: license invalid/expired.</p></div>'; }); } });
3. Enforce limits:
<?php $limits = Api::get_limits('smartdesk-core'); $maxMembers = (int)($limits['members.max'] ?? 0); if ($maxMembers > 0 && $currentCount > $maxMembers) { // Block or upsell }
4. (Optional) Enable updater in your plugin bootstrap:
\SmartDesk\LicenseClient\Updater::boot();
Build & Release
Create a production zip (no dev-deps, optimized autoload):
composer run build-zip
Artifacts:
smartdesk-license-client.zip
(from Composer archive)- Excludes tests, dotfiles, vendor test assets, etc.
Troubleshooting
- “Cannot select database” / “Access denied”
Ensure correct DB credentials and thatwp_tests
exists; if using LocalWP, include the port (e.g.127.0.0.1:10016
). **ABSPATH**
not defined /**wp-settings.php**
missing
SetWP_PHPUNIT__WP_DIR
to a valid WordPress core folder (the filewp-settings.php
must exist there).- HTTP 400
**rest_invalid_json**
from server
The client retries asapplication/x-www-form-urlencoded
. Check server handlers if it persists. - 4xx responses are not cached
This is intentional. 5xx/429 and transport errors are cached to avoid storms.
Security notes
- License key & server secret are stored encrypted (HKDF-derived key, salts or persisted random master).
- Admin UI never renders stored secrets.
- Logger automatically redacts sensitive keys/values.
License
GPL-3.0-or-later. See LICENSE
.
Maintainer
SmartDesk (Martin Kronsteiner) — https://kronsteiner.gmbh