Skip to content

Instantly share code, notes, and snippets.

@otakupahp
Last active July 13, 2026 14:36
Show Gist options
  • Select an option

  • Save otakupahp/6c0d70b40a4f2390dda0f3c2a0dfd024 to your computer and use it in GitHub Desktop.

Select an option

Save otakupahp/6c0d70b40a4f2390dda0f3c2a0dfd024 to your computer and use it in GitHub Desktop.
Broker file for otakupahp/sdk-license-manager-for-woocommerce
<?php
/**
* Standalone broker/proxy for SDK License Manager for WooCommerce 2.0.0.
*
* Copy this file to a private PHP hosting/server that you control, configure the
* constants below, and point the SDK broker URL to this file or to a rewrite that
* routes to this file.
*
* Supported SDK logical endpoints:
* - GET /licenses/{license_key}
* - GET /licenses/activate/{license_key}
* - GET /licenses/deactivate/{license_key}
*
* The broker forwards those endpoints to:
* {LMFWC_SITE_URL}/wp-json/lmfwc/v2/{logical_endpoint}
* and appends the real consumer credentials server-side.
*/
/** Real WordPress site URL where License Manager for WooCommerce is installed. */
const LMFWC_SITE_URL = 'https://licenses.example.com/wp-json/lmfwc/v2/';
/** Real License Manager for WooCommerce REST API consumer key. */
const LMFWC_CONSUMER_KEY = 'ck_replace_with_real_consumer_key';
/** Real License Manager for WooCommerce REST API consumer secret. */
const LMFWC_CONSUMER_SECRET = 'cs_replace_with_real_consumer_secret';
/**
* Optional allow-list of product IDs enforced by the broker after validation or activation.
* Leave empty to skip broker-side product checks and let the SDK check productId client-side.
*
* @var array<int, int|string>
*/
const LMFWC_ALLOWED_PRODUCT_IDS = array();
/**
* Optional public token to reduce casual abuse.
*
* SDK 2.0.0 appends logical endpoints directly to the configured broker URL and does not
* append custom query parameters or headers. Leave this empty unless your web server/rewrite
* layer injects the configured query parameter before PHP receives the request, or unless a
* future SDK/client wrapper explicitly sends it.
*/
const BROKER_PUBLIC_TOKEN = '';
/** Query parameter name checked when BROKER_PUBLIC_TOKEN is not empty. */
const BROKER_PUBLIC_TOKEN_QUERY_PARAM = 'broker_token';
/** Upstream request timeout in seconds. */
const BROKER_TIMEOUT_SECONDS = 5;
/** Maximum accepted logical endpoint length. */
const BROKER_MAX_ENDPOINT_LENGTH = 512;
/**
* Send a JSON response and terminate execution.
*
* @param int $status_code HTTP status code.
* @param array<string, mixed> $payload JSON payload.
*/
function broker_json_response( int $status_code, array $payload ): void {
http_response_code( $status_code );
header( 'Content-Type: application/json; charset=UTF-8' );
header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
header( 'X-Content-Type-Options: nosniff' );
echo json_encode( $payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
exit;
}
/**
* Send a safe JSON error response and terminate execution.
*
* @param int $status_code HTTP status code.
* @param string $message Safe public error message.
*/
function broker_error_response( int $status_code, string $message ): void {
broker_json_response(
$status_code,
array(
'success' => false,
'message' => $message,
'data' => array(
'status' => $status_code,
),
)
);
}
/**
* Extract the SDK logical endpoint from the current request.
*
* @return string
*/
function broker_get_logical_endpoint(): string {
$path = '';
if ( isset( $_SERVER['PATH_INFO'] ) && is_string( $_SERVER['PATH_INFO'] ) ) {
$path = $_SERVER['PATH_INFO'];
} elseif ( isset( $_SERVER['REQUEST_URI'] ) && is_string( $_SERVER['REQUEST_URI'] ) ) {
$request_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
$script_name = isset( $_SERVER['SCRIPT_NAME'] ) && is_string( $_SERVER['SCRIPT_NAME'] ) ? $_SERVER['SCRIPT_NAME'] : '';
$script_base = '' !== $script_name ? dirname( $script_name ) : '';
if ( is_string( $request_path ) ) {
if ( '' !== $script_name && str_starts_with( $request_path, $script_name ) ) {
$path = substr( $request_path, strlen( $script_name ) );
} elseif ( '' !== $script_base && '.' !== $script_base && str_starts_with( $request_path, rtrim( $script_base, '/' ) . '/' ) ) {
$path = substr( $request_path, strlen( rtrim( $script_base, '/' ) . '/' ) );
} else {
$path = $request_path;
}
}
}
$path = trim( $path, '/' );
if ( str_starts_with( $path, 'wp-json/lmfwc/v2/' ) ) {
$path = substr( $path, strlen( 'wp-json/lmfwc/v2/' ) );
} elseif ( str_starts_with( $path, 'lmfwc/v2/' ) ) {
$path = substr( $path, strlen( 'lmfwc/v2/' ) );
} elseif ( str_starts_with( $path, 'v2/' ) ) {
$path = substr( $path, strlen( 'v2/' ) );
}
return trim( $path, '/' );
}
/**
* Validate and normalize a logical SDK endpoint.
*
* @param string $endpoint Raw logical endpoint.
* @return string
*/
function broker_validate_endpoint( string $endpoint ): string {
if ( '' === $endpoint || strlen( $endpoint ) > BROKER_MAX_ENDPOINT_LENGTH ) {
broker_error_response( 404, 'Unsupported license endpoint.' );
}
if (
str_contains( $endpoint, '..' ) ||
str_contains( $endpoint, '\\' ) ||
str_contains( $endpoint, '://' ) ||
str_starts_with( $endpoint, '/' )
) {
broker_error_response( 400, 'Invalid license endpoint.' );
}
$license_key_pattern = '[A-Za-z0-9._~!$&\'()*+,;=:@%-]+';
$patterns = array(
'#^licenses/' . $license_key_pattern . '$#',
'#^licenses/activate/' . $license_key_pattern . '$#',
'#^licenses/deactivate/' . $license_key_pattern . '$#',
);
foreach ( $patterns as $pattern ) {
if ( 1 === preg_match( $pattern, $endpoint ) ) {
return $endpoint;
}
}
broker_error_response( 404, 'Unsupported license endpoint.' );
return '';
}
/**
* Build the fixed upstream LMFWC URL for an allowed logical endpoint.
*
* @param string $endpoint Validated logical endpoint.
* @return string
*/
function broker_build_upstream_url( string $endpoint ): string {
$site_url = rtrim( LMFWC_SITE_URL, '/' );
$parts = parse_url( $site_url );
if (
! is_array( $parts ) ||
empty( $parts['scheme'] ) ||
empty( $parts['host'] ) ||
! in_array( strtolower( (string) $parts['scheme'] ), array( 'http', 'https' ), true )
) {
broker_error_response( 500, 'Broker configuration error.' );
}
$query = http_build_query(
array(
'consumer_key' => LMFWC_CONSUMER_KEY,
'consumer_secret' => LMFWC_CONSUMER_SECRET,
),
'',
'&',
PHP_QUERY_RFC3986
);
return $site_url . '/wp-json/lmfwc/v2/' . $endpoint . '?' . $query;
}
/**
* Perform an upstream GET request using cURL when available, or PHP streams otherwise.
*
* @param string $url Fixed upstream URL.
* @return array{status: int, body: string}
*/
function broker_upstream_get( string $url ): array {
if ( function_exists( 'curl_init' ) ) {
$curl = curl_init( $url );
if ( false === $curl ) {
broker_error_response( 500, 'Unable to initialize upstream request.' );
}
curl_setopt_array(
$curl,
array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => BROKER_TIMEOUT_SECONDS,
CURLOPT_TIMEOUT => BROKER_TIMEOUT_SECONDS,
CURLOPT_HTTPHEADER => array(
'Accept: application/json',
'Content-Type: application/json; charset=UTF-8',
),
)
);
$body = curl_exec( $curl );
$status = (int) curl_getinfo( $curl, CURLINFO_RESPONSE_CODE );
$error = curl_errno( $curl );
curl_close( $curl );
if ( false === $body || 0 !== $error ) {
broker_error_response( 500, 'License server request failed.' );
}
return array(
'status' => $status > 0 ? $status : 500,
'body' => (string) $body,
);
}
$context = stream_context_create(
array(
'http' => array(
'method' => 'GET',
'timeout' => BROKER_TIMEOUT_SECONDS,
'ignore_errors' => true,
'header' => "Accept: application/json\r\nContent-Type: application/json; charset=UTF-8\r\n",
),
)
);
$body = file_get_contents( $url, false, $context );
if ( false === $body ) {
broker_error_response( 500, 'License server request failed.' );
}
$status = 500;
$headers = function_exists( 'http_get_last_response_headers' ) ? http_get_last_response_headers() : array();
$headers = is_array( $headers ) ? $headers : array();
foreach ( $headers as $header ) {
if ( is_string( $header ) && 1 === preg_match( '#^HTTP/\S+\s+(\d{3})#', $header, $matches ) ) {
$status = (int) $matches[1];
break;
}
}
return array(
'status' => $status,
'body' => (string) $body,
);
}
/**
* Enforce optional broker-side product allow-list on successful LMFWC responses.
*
* @param array<string, mixed> $payload Upstream JSON payload.
* @return array<string, mixed>
*/
function broker_apply_product_allow_list( array $payload ): array {
$allowed_product_ids = array_map( 'strval', LMFWC_ALLOWED_PRODUCT_IDS );
if ( empty( $allowed_product_ids ) || empty( $payload['success'] ) || ! isset( $payload['data'] ) || ! is_array( $payload['data'] ) ) {
return $payload;
}
$product_id = isset( $payload['data']['productId'] ) && is_scalar( $payload['data']['productId'] ) ? (string) $payload['data']['productId'] : '';
if ( '' !== $product_id && in_array( $product_id, $allowed_product_ids, true ) ) {
return $payload;
}
return array(
'success' => false,
'message' => 'The license is not allowed for this product.',
'data' => array(
'status' => 403,
),
);
}
if ( 'GET' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) {
broker_error_response( 405, 'Method not allowed.' );
}
if ( '' !== BROKER_PUBLIC_TOKEN ) {
$token = isset( $_GET[ BROKER_PUBLIC_TOKEN_QUERY_PARAM ] ) && is_scalar( $_GET[ BROKER_PUBLIC_TOKEN_QUERY_PARAM ] ) ? (string) $_GET[ BROKER_PUBLIC_TOKEN_QUERY_PARAM ] : '';
if ( ! hash_equals( BROKER_PUBLIC_TOKEN, $token ) ) {
broker_error_response( 403, 'Broker token rejected.' );
}
}
if ( '' === LMFWC_CONSUMER_KEY || '' === LMFWC_CONSUMER_SECRET ) {
broker_error_response( 500, 'Broker configuration error.' );
}
$endpoint = broker_validate_endpoint( broker_get_logical_endpoint() );
$url = broker_build_upstream_url( $endpoint );
$result = broker_upstream_get( $url );
$payload = json_decode( $result['body'], true );
if ( ! is_array( $payload ) ) {
broker_error_response( 500, 'License server returned an invalid response.' );
}
$status_code = in_array( $result['status'], array( 200, 201 ), true ) ? $result['status'] : ( $result['status'] > 0 ? $result['status'] : 500 );
$payload = broker_apply_product_allow_list( $payload );
if ( isset( $payload['data'] ) && is_array( $payload['data'] ) && isset( $payload['data']['status'] ) ) {
$payload_status = (int) $payload['data']['status'];
if ( $payload_status >= 400 && $payload_status <= 599 ) {
$status_code = $payload_status;
}
}
if ( 403 === (int) ( $payload['data']['status'] ?? 0 ) && false === (bool) ( $payload['success'] ?? false ) ) {
$status_code = 403;
}
broker_json_response( $status_code, $payload );
@otakupahp

Copy link
Copy Markdown
Author

This is a broker file sample that could be used by the "Broker mode" of https://github.com/otakupahp/sdk-license-manager-for-woocommerce

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment