Initial plugin setup (v0.0.1)
Some checks failed
Create Release Package / build-release (push) Failing after 48s

- Create initial WordPress plugin structure
- Add Prometheus metrics collector with default metrics
- Implement authenticated /metrics endpoint with Bearer token
- Add license management integration
- Create admin settings page under Settings > Metrics
- Set up Gitea CI/CD pipeline for automated releases
- Add extensibility via wp_prometheus_collect_metrics hook

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 15:31:21 +01:00
commit 7ff87f7c8d
21 changed files with 2890 additions and 0 deletions

496
src/License/Manager.php Normal file
View File

@@ -0,0 +1,496 @@
<?php
/**
* License management class.
*
* @package WP_Prometheus
*/
namespace Magdev\WpPrometheus\License;
use Magdev\WcLicensedProductClient\SecureLicenseClient;
use Magdev\WcLicensedProductClient\Dto\LicenseState;
use Magdev\WcLicensedProductClient\Exception\LicenseException;
use Magdev\WcLicensedProductClient\Exception\LicenseNotFoundException;
use Magdev\WcLicensedProductClient\Exception\LicenseExpiredException;
use Magdev\WcLicensedProductClient\Exception\LicenseRevokedException;
use Magdev\WcLicensedProductClient\Exception\LicenseInactiveException;
use Magdev\WcLicensedProductClient\Exception\DomainMismatchException;
use Magdev\WcLicensedProductClient\Exception\MaxActivationsReachedException;
use Magdev\WcLicensedProductClient\Exception\RateLimitExceededException;
use Magdev\WcLicensedProductClient\Security\SignatureException;
use Symfony\Component\HttpClient\HttpClient;
// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* License Manager class.
*
* Handles license validation, activation, and status checking.
*/
final class Manager {
/**
* Option names for license settings.
*/
public const OPTION_LICENSE_KEY = 'wp_prometheus_license_key';
public const OPTION_SERVER_URL = 'wp_prometheus_license_server_url';
public const OPTION_SERVER_SECRET = 'wp_prometheus_license_server_secret';
public const OPTION_LICENSE_STATUS = 'wp_prometheus_license_status';
public const OPTION_LICENSE_DATA = 'wp_prometheus_license_data';
public const OPTION_LAST_CHECK = 'wp_prometheus_license_last_check';
/**
* Transient name for caching license validation.
*/
private const TRANSIENT_LICENSE_CHECK = 'wp_prometheus_license_check';
/**
* Cache TTL in seconds (24 hours).
*/
private const CACHE_TTL = 86400;
/**
* Singleton instance.
*
* @var Manager|null
*/
private static ?Manager $instance = null;
/**
* License client instance.
*
* @var SecureLicenseClient|null
*/
private ?SecureLicenseClient $client = null;
/**
* Get singleton instance.
*
* @return Manager
*/
public static function get_instance(): Manager {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor.
*/
private function __construct() {
$this->init_hooks();
}
/**
* Initialize WordPress hooks.
*
* @return void
*/
private function init_hooks(): void {
add_action( 'wp_ajax_wp_prometheus_validate_license', array( $this, 'ajax_validate_license' ) );
add_action( 'wp_ajax_wp_prometheus_activate_license', array( $this, 'ajax_activate_license' ) );
add_action( 'wp_ajax_wp_prometheus_deactivate_license', array( $this, 'ajax_deactivate_license' ) );
add_action( 'wp_ajax_wp_prometheus_check_license_status', array( $this, 'ajax_check_status' ) );
}
/**
* Initialize the license client.
*
* @return bool True if client was initialized successfully.
*/
private function init_client(): bool {
if ( null !== $this->client ) {
return true;
}
$server_url = self::get_server_url();
$server_secret = self::get_server_secret();
if ( empty( $server_url ) || empty( $server_secret ) ) {
return false;
}
try {
$this->client = new SecureLicenseClient(
httpClient: HttpClient::create(),
baseUrl: $server_url,
serverSecret: $server_secret,
);
return true;
} catch ( \Throwable $e ) {
return false;
}
}
/**
* Validate the current license.
*
* @return array{success: bool, message: string, data?: array}
*/
public function validate(): array {
if ( ! $this->init_client() ) {
return array(
'success' => false,
'message' => __( 'License server configuration is incomplete.', 'wp-prometheus' ),
);
}
$license_key = self::get_license_key();
if ( empty( $license_key ) ) {
return array(
'success' => false,
'message' => __( 'No license key provided.', 'wp-prometheus' ),
);
}
$domain = wp_parse_url( home_url(), PHP_URL_HOST );
try {
$result = $this->client->validate( $license_key, $domain );
$this->update_cached_status( 'valid', array(
'product_id' => $result->productId,
'expires_at' => $result->expiresAt?->format( 'c' ),
'version_id' => $result->versionId,
) );
return array(
'success' => true,
'message' => __( 'License validated successfully.', 'wp-prometheus' ),
'data' => array(
'status' => 'valid',
'product_id' => $result->productId,
'expires_at' => $result->expiresAt?->format( 'Y-m-d' ),
'lifetime' => $result->isLifetime(),
),
);
} catch ( LicenseNotFoundException $e ) {
$this->update_cached_status( 'invalid' );
return array(
'success' => false,
'message' => __( 'License key not found.', 'wp-prometheus' ),
);
} catch ( LicenseExpiredException $e ) {
$this->update_cached_status( 'expired' );
return array(
'success' => false,
'message' => __( 'Your license has expired.', 'wp-prometheus' ),
);
} catch ( LicenseRevokedException $e ) {
$this->update_cached_status( 'revoked' );
return array(
'success' => false,
'message' => __( 'Your license has been revoked.', 'wp-prometheus' ),
);
} catch ( LicenseInactiveException $e ) {
$this->update_cached_status( 'inactive' );
return array(
'success' => false,
'message' => __( 'License is inactive. Please activate it first.', 'wp-prometheus' ),
);
} catch ( DomainMismatchException $e ) {
$this->update_cached_status( 'invalid' );
return array(
'success' => false,
'message' => __( 'This license is not activated for this domain.', 'wp-prometheus' ),
);
} catch ( SignatureException $e ) {
return array(
'success' => false,
'message' => __( 'License verification failed. Please check your server secret.', 'wp-prometheus' ),
);
} catch ( RateLimitExceededException $e ) {
return array(
'success' => false,
'message' => __( 'Too many requests. Please try again later.', 'wp-prometheus' ),
);
} catch ( LicenseException $e ) {
return array(
'success' => false,
'message' => sprintf(
/* translators: %s: Error message */
__( 'License validation failed: %s', 'wp-prometheus' ),
$e->getMessage()
),
);
} catch ( \Throwable $e ) {
return array(
'success' => false,
'message' => __( 'Unable to verify license. Please try again later.', 'wp-prometheus' ),
);
}
}
/**
* Activate the license for this domain.
*
* @return array{success: bool, message: string, data?: array}
*/
public function activate(): array {
if ( ! $this->init_client() ) {
return array(
'success' => false,
'message' => __( 'License server configuration is incomplete.', 'wp-prometheus' ),
);
}
$license_key = self::get_license_key();
if ( empty( $license_key ) ) {
return array(
'success' => false,
'message' => __( 'No license key provided.', 'wp-prometheus' ),
);
}
$domain = wp_parse_url( home_url(), PHP_URL_HOST );
try {
$result = $this->client->activate( $license_key, $domain );
if ( $result->success ) {
return $this->validate();
}
return array(
'success' => false,
'message' => $result->message,
);
} catch ( MaxActivationsReachedException $e ) {
return array(
'success' => false,
'message' => __( 'Maximum activations reached. Please deactivate another site.', 'wp-prometheus' ),
);
} catch ( LicenseNotFoundException $e ) {
return array(
'success' => false,
'message' => __( 'License key not found.', 'wp-prometheus' ),
);
} catch ( LicenseExpiredException $e ) {
return array(
'success' => false,
'message' => __( 'Your license has expired.', 'wp-prometheus' ),
);
} catch ( SignatureException $e ) {
return array(
'success' => false,
'message' => __( 'License verification failed. Please check your server secret.', 'wp-prometheus' ),
);
} catch ( LicenseException $e ) {
return array(
'success' => false,
'message' => sprintf(
/* translators: %s: Error message */
__( 'License activation failed: %s', 'wp-prometheus' ),
$e->getMessage()
),
);
} catch ( \Throwable $e ) {
return array(
'success' => false,
'message' => __( 'Unable to activate license. Please try again later.', 'wp-prometheus' ),
);
}
}
/**
* Check if the license is currently valid.
*
* @return bool
*/
public static function is_license_valid(): bool {
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
return 'valid' === $status;
}
/**
* Get the license key.
*
* @return string
*/
public static function get_license_key(): string {
return get_option( self::OPTION_LICENSE_KEY, '' );
}
/**
* Get the license server URL.
*
* @return string
*/
public static function get_server_url(): string {
return get_option( self::OPTION_SERVER_URL, '' );
}
/**
* Get the server secret.
*
* @return string
*/
public static function get_server_secret(): string {
return get_option( self::OPTION_SERVER_SECRET, '' );
}
/**
* Get cached license status.
*
* @return string
*/
public static function get_cached_status(): string {
return get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
}
/**
* Get cached license data.
*
* @return array
*/
public static function get_cached_data(): array {
return get_option( self::OPTION_LICENSE_DATA, array() );
}
/**
* Get last check timestamp.
*
* @return int
*/
public static function get_last_check(): int {
return (int) get_option( self::OPTION_LAST_CHECK, 0 );
}
/**
* Save license settings.
*
* @param array $data Settings data.
* @return bool
*/
public static function save_settings( array $data ): bool {
if ( isset( $data['license_key'] ) ) {
update_option( self::OPTION_LICENSE_KEY, sanitize_text_field( $data['license_key'] ) );
}
if ( isset( $data['server_url'] ) ) {
update_option( self::OPTION_SERVER_URL, esc_url_raw( $data['server_url'] ) );
}
if ( isset( $data['server_secret'] ) ) {
$secret = sanitize_text_field( $data['server_secret'] );
if ( ! empty( $secret ) ) {
update_option( self::OPTION_SERVER_SECRET, $secret );
}
}
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
delete_transient( self::TRANSIENT_LICENSE_CHECK );
return true;
}
/**
* Update cached license status.
*
* @param string $status Status value.
* @param array $data Additional data.
* @return void
*/
private function update_cached_status( string $status, array $data = array() ): void {
update_option( self::OPTION_LICENSE_STATUS, $status );
update_option( self::OPTION_LICENSE_DATA, $data );
update_option( self::OPTION_LAST_CHECK, time() );
}
/**
* AJAX handler: Validate license.
*
* @return void
*/
public function ajax_validate_license(): void {
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
) );
}
$result = $this->validate();
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
/**
* AJAX handler: Activate license.
*
* @return void
*/
public function ajax_activate_license(): void {
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
) );
}
$result = $this->activate();
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
/**
* AJAX handler: Deactivate license.
*
* @return void
*/
public function ajax_deactivate_license(): void {
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
) );
}
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
update_option( self::OPTION_LICENSE_DATA, array() );
delete_transient( self::TRANSIENT_LICENSE_CHECK );
wp_send_json_success( array(
'success' => true,
'message' => __( 'License deactivated.', 'wp-prometheus' ),
) );
}
/**
* AJAX handler: Check license status.
*
* @return void
*/
public function ajax_check_status(): void {
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
) );
}
$result = $this->validate();
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
}