getMessage(); } $action = strtolower(trim((string)($_GET['action'] ?? ''))); $input = clm_request_data(); if ($action !== '') { if ($schemaError !== '') { clm_json(['success' => false, 'error' => $schemaError], 500); } try { $tables = clm_tables(); $licensesTable = $tables['licenses']; $activationsTable = $tables['activations']; $appsTable = $tables['apps']; switch ($action) { case 'health': clm_json([ 'success' => true, 'manager' => clm_cfg('manager_name'), 'mode' => clm_manager_mode_label(), 'base_url' => clm_base_url(), 'tables' => $tables, ]); break; case 'activate': $key = strtoupper(trim((string)($input['license_key'] ?? ''))); $fingerprint = trim((string)($input['fingerprint'] ?? '')); $domain = trim((string)($input['domain'] ?? '')); $product = trim((string)($input['product'] ?? '')); $productVersion = trim((string)($input['product_version'] ?? '')); $requestedAppSlug = trim((string)($input['app_slug'] ?? '')); if ($key === '' || $fingerprint === '') { clm_json(['success' => false, 'error' => 'Missing required parameters.'], 422); } $license = clm_license_with_app_by_key($key); if (!$license) { clm_json(['success' => false, 'error' => 'Invalid license key.'], 404); } if ($requestedAppSlug !== '' && clm_slugify($requestedAppSlug) !== (string)$license['app_slug']) { clm_json(['success' => false, 'error' => 'License does not belong to this app.'], 409); } if (($license['status'] ?? 'suspended') !== 'active') { clm_json(['success' => false, 'error' => 'License is ' . $license['status'] . '.'], 409); } if (clm_license_is_expired($license)) { clm_json(['success' => false, 'error' => 'License is expired.'], 409); } $activation = clm_upsert_activation($license, $fingerprint, $domain, $product, $productVersion); if (empty($activation['success'])) { clm_json($activation, 409); } clm_json([ 'success' => true, 'activation_token' => $activation['activation_token'], 'max_activations' => (int)$license['max_activations'], 'allowed_activations' => (int)$license['max_activations'], 'max_counters' => (int)$license['max_counters'], 'current_activations' => clm_active_activation_count((int)$license['id']), 'app_slug' => (string)$license['app_slug'], 'app_name' => (string)$license['app_name'], ]); break; case 'verify': $key = strtoupper(trim((string)($input['license_key'] ?? ''))); $fingerprint = trim((string)($input['fingerprint'] ?? '')); $token = trim((string)($input['token'] ?? '')); $requestedAppSlug = trim((string)($input['app_slug'] ?? '')); if ($key === '' || $fingerprint === '' || $token === '') { clm_json(['success' => false, 'error' => 'Missing required parameters.'], 422); } $license = clm_license_with_app_by_key($key); if (!$license) { clm_json(['success' => false, 'error' => 'Invalid license key.'], 404); } if ($requestedAppSlug !== '' && clm_slugify($requestedAppSlug) !== (string)$license['app_slug']) { clm_json(['success' => false, 'error' => 'License does not belong to this app.'], 409); } if (!hash_equals(clm_make_token($key, $fingerprint), $token)) { clm_json(['success' => false, 'error' => 'Invalid activation token.'], 403); } if (($license['status'] ?? 'suspended') !== 'active' || clm_license_is_expired($license)) { clm_json(['success' => false, 'error' => 'License is no longer active.'], 409); } $stmt = clm_db()->prepare("SELECT * FROM `{$activationsTable}` WHERE license_id = ? AND fingerprint = ? AND status = 'active' LIMIT 1"); $stmt->execute([(int)$license['id'], $fingerprint]); $activation = $stmt->fetch(); if (!$activation) { clm_json(['success' => false, 'error' => 'Activation not found.'], 404); } $stmt = clm_db()->prepare("UPDATE `{$activationsTable}` SET last_seen_at = NOW() WHERE id = ?"); $stmt->execute([(int)$activation['id']]); clm_json([ 'success' => true, 'max_activations' => (int)$license['max_activations'], 'allowed_activations' => (int)$license['max_activations'], 'max_counters' => (int)$license['max_counters'], 'current_activations' => clm_active_activation_count((int)$license['id']), 'app_slug' => (string)$license['app_slug'], 'app_name' => (string)$license['app_name'], ]); break; case 'deactivate': $key = strtoupper(trim((string)($input['license_key'] ?? ''))); $fingerprint = trim((string)($input['fingerprint'] ?? '')); if ($key === '' || $fingerprint === '') { clm_json(['success' => false, 'error' => 'Missing required parameters.'], 422); } $license = clm_license_with_app_by_key($key); if (!$license) { clm_json(['success' => false, 'error' => 'Invalid license key.'], 404); } $stmt = clm_db()->prepare("SELECT * FROM `{$activationsTable}` WHERE license_id = ? AND fingerprint = ? LIMIT 1"); $stmt->execute([(int)$license['id'], $fingerprint]); $activation = $stmt->fetch(); if (!$activation) { clm_json(['success' => false, 'error' => 'Activation not found.'], 404); } if (($activation['status'] ?? 'active') !== 'active') { clm_json(['success' => true, 'already_inactive' => true]); } $stmt = clm_db()->prepare("UPDATE `{$activationsTable}` SET status = 'deactivated', deactivated_at = NOW(), last_seen_at = NOW() WHERE id = ?"); $stmt->execute([(int)$activation['id']]); clm_json(['success' => true]); break; case 'issue': if (!clm_secret_valid($input['secret'] ?? '')) { clm_json(['success' => false, 'error' => 'Unauthorized.'], 403); } $license = clm_issue_license_record($input); clm_json([ 'success' => true, 'license_key' => $license['license_key'], 'app_slug' => $license['app_slug'], 'app_name' => $license['app_name'], 'max_activations' => (int)$license['max_activations'], 'allowed_activations' => (int)$license['max_activations'], 'max_counters' => (int)$license['max_counters'], 'owner' => $license['owner'], 'address' => $license['address'], 'customer_name' => $license['customer_name'], 'customer_email' => $license['customer_email'], 'expires_at' => $license['expires_at'], ]); break; case 'list': if (!clm_secret_valid($input['secret'] ?? '')) { clm_json(['success' => false, 'error' => 'Unauthorized.'], 403); } $stmt = clm_db()->query("SELECT l.*, a.slug AS app_slug, a.name AS app_name, (SELECT COUNT(*) FROM `{$activationsTable}` act WHERE act.license_id = l.id AND act.status = 'active') AS current_activations FROM `{$licensesTable}` l INNER JOIN `{$appsTable}` a ON a.id = l.app_id ORDER BY l.created_at DESC"); clm_json([ 'success' => true, 'data' => $stmt->fetchAll(), ]); break; case 'apps': if (!clm_secret_valid($input['secret'] ?? '')) { clm_json(['success' => false, 'error' => 'Unauthorized.'], 403); } $stmt = clm_db()->query("SELECT a.*, (SELECT COUNT(*) FROM `{$licensesTable}` l WHERE l.app_id = a.id) AS licenses_count, (SELECT COUNT(*) FROM `{$activationsTable}` act INNER JOIN `{$licensesTable}` l2 ON l2.id = act.license_id WHERE l2.app_id = a.id AND act.status = 'active') AS active_activations FROM `{$appsTable}` a ORDER BY a.name ASC"); clm_json([ 'success' => true, 'data' => $stmt->fetchAll(), ]); break; case 'update': if (!clm_secret_valid($input['secret'] ?? '')) { clm_json(['success' => false, 'error' => 'Unauthorized.'], 403); } $license = null; if (!empty($input['id'])) { $license = clm_license_with_app_by_id((int)$input['id']); } elseif (!empty($input['license_key'])) { $license = clm_license_with_app_by_key((string)$input['license_key']); } if (!$license) { clm_json(['success' => false, 'error' => 'License not found.'], 404); } $updated = clm_update_license_record($license, $input); clm_json([ 'success' => true, 'data' => $updated, ]); break; default: clm_json(['success' => false, 'error' => 'Invalid endpoint.'], 404); } } catch (Throwable $e) { clm_json(['success' => false, 'error' => $e->getMessage()], 500); } } $stats = [ 'apps' => 0, 'licenses' => 0, 'activations' => 0, ]; if ($schemaError === '') { try { $tables = clm_tables(); $stats['apps'] = (int)clm_db()->query("SELECT COUNT(*) FROM `{$tables['apps']}`")->fetchColumn(); $stats['licenses'] = (int)clm_db()->query("SELECT COUNT(*) FROM `{$tables['licenses']}`")->fetchColumn(); $stats['activations'] = (int)clm_db()->query("SELECT COUNT(*) FROM `{$tables['activations']}` WHERE status = 'active'")->fetchColumn(); } catch (Throwable $e) { $schemaError = $e->getMessage(); } } $baseUrl = clm_base_url(); ?> Central License Manager | Multi-App Activation Hub
Standalone module

One activation hub for all your apps.

This folder is isolated from the legacy license_manager/ code, auto-creates its own multi-app tables, and can be copied to another server later without rewriting the rest of your app first.

Apps
Licenses
Active installs

What this module adds

  • Dedicated clm_apps, clm_licenses, and clm_activations tables.
  • Backward-compatible API responses for existing clients expecting allowed_activations.
  • Move-friendly structure so you can copy this folder to a separate host later.

Quick start

  1. Open install.php once.
  2. Open manage.php and create your app records.
  3. Point each client app to this folder through LICENSE_API_URL.

Available endpoints

  • index.php?action=health
  • index.php?action=activate
  • index.php?action=verify
  • index.php?action=deactivate
  • index.php?action=issue / list / update