mkr/smartdesk-license-client

License and policy Client-SDK for licensing and updates.

1.0.0 2025-09-01 12:43 UTC

This package is auto-updated.

Last update: 2025-09-04 06:05:52 UTC


README

License: GPL v3 PHP Version Packagist Version Packagist Downloads WordPress

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 PluginsSmartDesk 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’s home_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 that wp_tests exists; if using LocalWP, include the port (e.g. 127.0.0.1:10016).
  • **ABSPATH** not defined / **wp-settings.php** missing
    Set WP_PHPUNIT__WP_DIR to a valid WordPress core folder (the file wp-settings.php must exist there).
  • HTTP 400 **rest_invalid_json** from server
    The client retries as application/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