Initial plugin setup (v0.0.1)
All checks were successful
Create Release Package / build-release (push) Successful in 1m21s

- Main plugin file with PHP 8.3+ and WordPress 6.0+ version checks
- Plugin singleton class with admin menu and settings pages
- License Manager integration with SecureLicenseClient
- License settings tab with validation and activation
- Admin CSS and JavaScript for license management
- Gitea CI/CD workflow for automated releases
- Documentation: README.md, PLAN.md, CHANGELOG.md, CLAUDE.md
- Composer dependencies: Twig 3.0, license client
- Git submodule for wc-licensed-product-client library

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 13:15:13 +01:00
commit d36b6c3dd9
18 changed files with 3615 additions and 0 deletions

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

@@ -0,0 +1,533 @@
<?php
/**
* License Manager class.
*
* @package Magdev\WpBnb\License
*/
declare( strict_types=1 );
namespace Magdev\WpBnb\License;
use Magdev\WcLicensedProductClient\SecureLicenseClient;
use Magdev\WcLicensedProductClient\LicenseClient;
use Magdev\WcLicensedProductClient\DTO\LicenseState;
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;
/**
* Manages license validation and activation.
*/
final class Manager {
// Option keys.
public const OPTION_LICENSE_KEY = 'wp_bnb_license_key';
public const OPTION_SERVER_URL = 'wp_bnb_license_server_url';
public const OPTION_SERVER_SECRET = 'wp_bnb_license_server_secret';
public const OPTION_LICENSE_STATUS = 'wp_bnb_license_status';
public const OPTION_LICENSE_DATA = 'wp_bnb_license_data';
public const OPTION_LAST_CHECK = 'wp_bnb_license_last_check';
// Transient keys.
private const TRANSIENT_LICENSE_CHECK = 'wp_bnb_license_check';
// Cache TTL (24 hours).
private const CACHE_TTL = 86400;
/**
* Singleton instance.
*
* @var Manager|null
*/
private static ?Manager $instance = null;
/**
* License client.
*
* @var SecureLicenseClient|LicenseClient|null
*/
private SecureLicenseClient|LicenseClient|null $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 {
// AJAX handlers.
add_action( 'wp_ajax_wp_bnb_validate_license', array( $this, 'ajax_validate_license' ) );
add_action( 'wp_ajax_wp_bnb_activate_license', array( $this, 'ajax_activate_license' ) );
add_action( 'wp_ajax_wp_bnb_check_status', array( $this, 'ajax_check_status' ) );
}
/**
* Initialize the license client.
*
* @return bool
*/
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 ) ) {
return false;
}
try {
// Use SecureLicenseClient if server secret is configured.
if ( ! empty( $server_secret ) ) {
$this->client = new SecureLicenseClient(
httpClient: HttpClient::create(),
baseUrl: $server_url,
serverSecret: $server_secret,
);
} else {
$this->client = new LicenseClient(
httpClient: HttpClient::create(),
baseUrl: $server_url,
);
}
return true;
} catch ( \Throwable $e ) {
return false;
}
}
/**
* Check if license is valid.
*
* @return bool
*/
public static function is_license_valid(): bool {
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
return 'valid' === $status;
}
/**
* Get license key.
*
* @return string
*/
public static function get_license_key(): string {
return get_option( self::OPTION_LICENSE_KEY, '' );
}
/**
* Get server URL.
*
* @return string
*/
public static function get_server_url(): string {
return get_option( self::OPTION_SERVER_URL, '' );
}
/**
* Get 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 {
$server_url = self::get_server_url();
$license_key = self::get_license_key();
if ( empty( $server_url ) || empty( $license_key ) ) {
return 'unconfigured';
}
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'] ) );
}
// Only update secret if provided (not empty).
if ( ! empty( $data['server_secret'] ) ) {
update_option( self::OPTION_SERVER_SECRET, sanitize_text_field( $data['server_secret'] ) );
}
// Clear cached status when settings change.
delete_transient( self::TRANSIENT_LICENSE_CHECK );
return true;
}
/**
* Clear all license data.
*
* @return void
*/
public static function clear_license_data(): void {
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
update_option( self::OPTION_LICENSE_DATA, array() );
update_option( self::OPTION_LAST_CHECK, 0 );
delete_transient( self::TRANSIENT_LICENSE_CHECK );
}
/**
* Validate license.
*
* @return array
*/
public function validate(): array {
if ( ! $this->init_client() ) {
return array(
'success' => false,
'message' => __( 'License client could not be initialized. Please check server URL and secret.', 'wp-bnb' ),
'status' => 'unconfigured',
);
}
$license_key = self::get_license_key();
if ( empty( $license_key ) ) {
return array(
'success' => false,
'message' => __( 'Please enter a license key.', 'wp-bnb' ),
'status' => 'unconfigured',
);
}
$domain = $this->get_current_domain();
try {
$license_info = $this->client->validate( $license_key, $domain );
// Store license data.
$license_data = array(
'product_id' => $license_info->productId,
'expires' => $license_info->expiresAt?->format( 'Y-m-d H:i:s' ),
);
update_option( self::OPTION_LICENSE_STATUS, 'valid' );
update_option( self::OPTION_LICENSE_DATA, $license_data );
update_option( self::OPTION_LAST_CHECK, time() );
set_transient( self::TRANSIENT_LICENSE_CHECK, 'valid', self::CACHE_TTL );
return array(
'success' => true,
'message' => __( 'License is valid.', 'wp-bnb' ),
'status' => 'valid',
'data' => $license_data,
);
} catch ( LicenseNotFoundException $e ) {
return $this->handle_license_error( 'invalid', __( 'License key not found.', 'wp-bnb' ) );
} catch ( LicenseExpiredException $e ) {
return $this->handle_license_error( 'expired', __( 'License has expired.', 'wp-bnb' ) );
} catch ( LicenseRevokedException $e ) {
return $this->handle_license_error( 'revoked', __( 'License has been revoked.', 'wp-bnb' ) );
} catch ( LicenseInactiveException $e ) {
return $this->handle_license_error( 'inactive', __( 'License is inactive. Please activate it first.', 'wp-bnb' ) );
} catch ( DomainMismatchException $e ) {
return $this->handle_license_error( 'invalid', __( 'License is not valid for this domain.', 'wp-bnb' ) );
} catch ( SignatureException $e ) {
return $this->handle_license_error( 'invalid', __( 'Response signature verification failed. Please check server secret.', 'wp-bnb' ) );
} catch ( RateLimitExceededException $e ) {
return array(
'success' => false,
'message' => sprintf(
/* translators: %d: Seconds to wait */
__( 'Rate limit exceeded. Please try again in %d seconds.', 'wp-bnb' ),
$e->retryAfter
),
'status' => self::get_cached_status(),
);
} catch ( \Throwable $e ) {
return $this->handle_license_error( 'invalid', __( 'An error occurred while validating the license.', 'wp-bnb' ) );
}
}
/**
* Activate license.
*
* @return array
*/
public function activate(): array {
if ( ! $this->init_client() ) {
return array(
'success' => false,
'message' => __( 'License client could not be initialized. Please check server URL and secret.', 'wp-bnb' ),
'status' => 'unconfigured',
);
}
$license_key = self::get_license_key();
if ( empty( $license_key ) ) {
return array(
'success' => false,
'message' => __( 'Please enter a license key.', 'wp-bnb' ),
'status' => 'unconfigured',
);
}
$domain = $this->get_current_domain();
try {
$result = $this->client->activate( $license_key, $domain );
if ( $result->success ) {
// Validate after activation to get full license info.
return $this->validate();
}
return array(
'success' => false,
'message' => __( 'Activation failed.', 'wp-bnb' ),
'status' => 'inactive',
);
} catch ( MaxActivationsReachedException $e ) {
return $this->handle_license_error( 'invalid', __( 'Maximum activations reached for this license.', 'wp-bnb' ) );
} catch ( LicenseNotFoundException $e ) {
return $this->handle_license_error( 'invalid', __( 'License key not found.', 'wp-bnb' ) );
} catch ( LicenseExpiredException $e ) {
return $this->handle_license_error( 'expired', __( 'License has expired.', 'wp-bnb' ) );
} catch ( SignatureException $e ) {
return $this->handle_license_error( 'invalid', __( 'Response signature verification failed. Please check server secret.', 'wp-bnb' ) );
} catch ( RateLimitExceededException $e ) {
return array(
'success' => false,
'message' => sprintf(
/* translators: %d: Seconds to wait */
__( 'Rate limit exceeded. Please try again in %d seconds.', 'wp-bnb' ),
$e->retryAfter
),
'status' => self::get_cached_status(),
);
} catch ( \Throwable $e ) {
return $this->handle_license_error( 'invalid', __( 'An error occurred while activating the license.', 'wp-bnb' ) );
}
}
/**
* Get license status.
*
* @param bool $force_refresh Whether to force a refresh from server.
* @return array
*/
public function get_status( bool $force_refresh = false ): array {
// Check transient cache first.
if ( ! $force_refresh ) {
$cached = get_transient( self::TRANSIENT_LICENSE_CHECK );
if ( false !== $cached ) {
return array(
'success' => 'valid' === $cached,
'status' => $cached,
'data' => self::get_cached_data(),
'cached' => true,
);
}
}
if ( ! $this->init_client() ) {
return array(
'success' => false,
'status' => 'unconfigured',
'message' => __( 'License client not configured.', 'wp-bnb' ),
);
}
$license_key = self::get_license_key();
if ( empty( $license_key ) ) {
return array(
'success' => false,
'status' => 'unconfigured',
'message' => __( 'No license key configured.', 'wp-bnb' ),
);
}
try {
$status_info = $this->client->status( $license_key );
$status = match ( $status_info->status ) {
LicenseState::Active => 'valid',
LicenseState::Inactive => 'inactive',
LicenseState::Expired => 'expired',
LicenseState::Revoked => 'revoked',
};
update_option( self::OPTION_LICENSE_STATUS, $status );
update_option( self::OPTION_LAST_CHECK, time() );
set_transient( self::TRANSIENT_LICENSE_CHECK, $status, self::CACHE_TTL );
return array(
'success' => 'valid' === $status,
'status' => $status,
'data' => self::get_cached_data(),
'cached' => false,
);
} catch ( \Throwable $e ) {
return array(
'success' => false,
'status' => 'invalid',
'message' => __( 'Failed to check license status.', 'wp-bnb' ),
);
}
}
/**
* Handle license error.
*
* @param string $status Status code.
* @param string $message Error message.
* @return array
*/
private function handle_license_error( string $status, string $message ): array {
update_option( self::OPTION_LICENSE_STATUS, $status );
update_option( self::OPTION_LAST_CHECK, time() );
delete_transient( self::TRANSIENT_LICENSE_CHECK );
return array(
'success' => false,
'message' => $message,
'status' => $status,
);
}
/**
* Get current domain.
*
* @return string
*/
private function get_current_domain(): string {
$site_url = get_site_url();
$parsed = wp_parse_url( $site_url );
return $parsed['host'] ?? '';
}
/**
* AJAX handler: Validate license.
*
* @return void
*/
public function ajax_validate_license(): void {
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
) );
}
$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_bnb_admin_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
) );
}
$result = $this->activate();
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
/**
* AJAX handler: Check status.
*
* @return void
*/
public function ajax_check_status(): void {
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array(
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
) );
}
$force_refresh = isset( $_POST['force'] ) && 'true' === $_POST['force'];
$result = $this->get_status( $force_refresh );
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
}

617
src/Plugin.php Normal file
View File

@@ -0,0 +1,617 @@
<?php
/**
* Main Plugin class.
*
* @package Magdev\WpBnb
*/
declare( strict_types=1 );
namespace Magdev\WpBnb;
use Magdev\WpBnb\License\Manager as LicenseManager;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
/**
* Main Plugin singleton class.
*/
final class Plugin {
/**
* Plugin instance.
*
* @var Plugin|null
*/
private static ?Plugin $instance = null;
/**
* Twig environment.
*
* @var Environment|null
*/
private ?Environment $twig = null;
/**
* Get plugin instance.
*
* @return Plugin
*/
public static function get_instance(): Plugin {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor to enforce singleton.
*/
private function __construct() {
$this->init_hooks();
$this->init_components();
}
/**
* Initialize WordPress hooks.
*
* @return void
*/
private function init_hooks(): void {
// Load text domain.
add_action( 'init', array( $this, 'load_textdomain' ) );
// Register assets.
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
// Add plugin action links.
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, array( $this, 'add_action_links' ) );
}
/**
* Initialize plugin components.
*
* @return void
*/
private function init_components(): void {
// Initialize License Manager (always active for admin).
LicenseManager::get_instance();
// Initialize admin components.
if ( is_admin() ) {
$this->init_admin();
}
// Initialize frontend components only if licensed.
if ( ! is_admin() && LicenseManager::is_license_valid() ) {
$this->init_frontend();
}
}
/**
* Initialize admin components.
*
* @return void
*/
private function init_admin(): void {
// Admin menu and settings will be added here.
add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
}
/**
* Initialize frontend components.
*
* @return void
*/
private function init_frontend(): void {
// Frontend shortcodes, blocks, and widgets will be added here.
}
/**
* Load plugin text domain.
*
* @return void
*/
public function load_textdomain(): void {
load_plugin_textdomain(
'wp-bnb',
false,
dirname( WP_BNB_BASENAME ) . '/languages'
);
}
/**
* Enqueue admin assets.
*
* @param string $hook_suffix Current admin page hook.
* @return void
*/
public function enqueue_admin_assets( string $hook_suffix ): void {
// Only load on plugin pages.
if ( strpos( $hook_suffix, 'wp-bnb' ) === false ) {
return;
}
wp_enqueue_style(
'wp-bnb-admin',
WP_BNB_URL . 'assets/css/admin.css',
array(),
WP_BNB_VERSION
);
wp_enqueue_script(
'wp-bnb-admin',
WP_BNB_URL . 'assets/js/admin.js',
array( 'jquery' ),
WP_BNB_VERSION,
true
);
wp_localize_script(
'wp-bnb-admin',
'wpBnbAdmin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_bnb_admin_nonce' ),
'i18n' => array(
'validating' => __( 'Validating...', 'wp-bnb' ),
'activating' => __( 'Activating...', 'wp-bnb' ),
'error' => __( 'An error occurred. Please try again.', 'wp-bnb' ),
),
)
);
}
/**
* Enqueue frontend assets.
*
* @return void
*/
public function enqueue_frontend_assets(): void {
// Only load if licensed.
if ( ! LicenseManager::is_license_valid() ) {
return;
}
wp_enqueue_style(
'wp-bnb-frontend',
WP_BNB_URL . 'assets/css/frontend.css',
array(),
WP_BNB_VERSION
);
wp_enqueue_script(
'wp-bnb-frontend',
WP_BNB_URL . 'assets/js/frontend.js',
array(),
WP_BNB_VERSION,
true
);
}
/**
* Add plugin action links.
*
* @param array $links Existing plugin links.
* @return array
*/
public function add_action_links( array $links ): array {
$plugin_links = array(
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-bnb-settings' ) ) . '">' . esc_html__( 'Settings', 'wp-bnb' ) . '</a>',
);
return array_merge( $plugin_links, $links );
}
/**
* Register admin menu.
*
* @return void
*/
public function register_admin_menu(): void {
// Main menu.
add_menu_page(
__( 'WP BnB', 'wp-bnb' ),
__( 'WP BnB', 'wp-bnb' ),
'manage_options',
'wp-bnb',
array( $this, 'render_dashboard_page' ),
'dashicons-building',
30
);
// Dashboard submenu.
add_submenu_page(
'wp-bnb',
__( 'Dashboard', 'wp-bnb' ),
__( 'Dashboard', 'wp-bnb' ),
'manage_options',
'wp-bnb',
array( $this, 'render_dashboard_page' )
);
// Settings submenu.
add_submenu_page(
'wp-bnb',
__( 'Settings', 'wp-bnb' ),
__( 'Settings', 'wp-bnb' ),
'manage_options',
'wp-bnb-settings',
array( $this, 'render_settings_page' )
);
}
/**
* Register plugin settings.
*
* @return void
*/
public function register_settings(): void {
// License settings are handled by LicenseManager.
// Additional settings will be added here.
}
/**
* Render dashboard page.
*
* @return void
*/
public function render_dashboard_page(): void {
$license_status = LicenseManager::get_cached_status();
?>
<div class="wrap">
<h1><?php esc_html_e( 'WP BnB Dashboard', 'wp-bnb' ); ?></h1>
<?php if ( 'valid' !== $license_status ) : ?>
<div class="notice notice-warning">
<p>
<?php
printf(
/* translators: %s: Link to settings page */
esc_html__( 'Your license is not active. Please %s to unlock all features.', 'wp-bnb' ),
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=license' ) ) . '">' . esc_html__( 'activate your license', 'wp-bnb' ) . '</a>'
);
?>
</p>
</div>
<?php endif; ?>
<div class="wp-bnb-dashboard">
<p><?php esc_html_e( 'Welcome to WP BnB Management. Use the menu on the left to manage your buildings, rooms, bookings, and guests.', 'wp-bnb' ); ?></p>
</div>
</div>
<?php
}
/**
* Render settings page.
*
* @return void
*/
public function render_settings_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab switching only.
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
// Handle form submission.
if ( isset( $_POST['wp_bnb_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_bnb_settings_nonce'] ), 'wp_bnb_save_settings' ) ) {
$this->save_settings( $active_tab );
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'WP BnB Settings', 'wp-bnb' ); ?></h1>
<nav class="nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=general' ) ); ?>"
class="nav-tab <?php echo 'general' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=license' ) ); ?>"
class="nav-tab <?php echo 'license' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'License', 'wp-bnb' ); ?>
</a>
</nav>
<div class="tab-content">
<?php
switch ( $active_tab ) {
case 'license':
$this->render_license_settings();
break;
default:
$this->render_general_settings();
break;
}
?>
</div>
</div>
<?php
}
/**
* Render general settings tab.
*
* @return void
*/
private function render_general_settings(): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_business_name"><?php esc_html_e( 'Business Name', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_business_name" id="wp_bnb_business_name"
value="<?php echo esc_attr( get_option( 'wp_bnb_business_name', '' ) ); ?>"
class="regular-text">
<p class="description"><?php esc_html_e( 'The name of your B&B business.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_currency"><?php esc_html_e( 'Currency', 'wp-bnb' ); ?></label>
</th>
<td>
<select name="wp_bnb_currency" id="wp_bnb_currency">
<?php
$currencies = array(
'CHF' => __( 'Swiss Franc (CHF)', 'wp-bnb' ),
'EUR' => __( 'Euro (EUR)', 'wp-bnb' ),
'USD' => __( 'US Dollar (USD)', 'wp-bnb' ),
'GBP' => __( 'British Pound (GBP)', 'wp-bnb' ),
);
$current = get_option( 'wp_bnb_currency', 'CHF' );
foreach ( $currencies as $code => $label ) :
?>
<option value="<?php echo esc_attr( $code ); ?>" <?php selected( $current, $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Settings', 'wp-bnb' ) ); ?>
</form>
<?php
}
/**
* Render license settings tab.
*
* @return void
*/
private function render_license_settings(): void {
$license_key = LicenseManager::get_license_key();
$server_url = LicenseManager::get_server_url();
$license_status = LicenseManager::get_cached_status();
$license_data = LicenseManager::get_cached_data();
$last_check = LicenseManager::get_last_check();
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<h2><?php esc_html_e( 'License Status', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
<td>
<?php $this->render_license_status_badge( $license_status ); ?>
<?php if ( $last_check > 0 ) : ?>
<p class="description">
<?php
printf(
/* translators: %s: Time ago string */
esc_html__( 'Last checked: %s', 'wp-bnb' ),
esc_html( human_time_diff( $last_check, time() ) . ' ' . __( 'ago', 'wp-bnb' ) )
);
?>
</p>
<?php endif; ?>
</td>
</tr>
<?php if ( ! empty( $license_data['expires'] ) ) : ?>
<tr>
<th scope="row"><?php esc_html_e( 'Expires', 'wp-bnb' ); ?></th>
<td>
<?php echo esc_html( wp_date( get_option( 'date_format' ), strtotime( $license_data['expires'] ) ) ); ?>
</td>
</tr>
<?php endif; ?>
</table>
<h2><?php esc_html_e( 'License Configuration', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_license_server_url"><?php esc_html_e( 'License Server URL', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_license_server_url" id="wp_bnb_license_server_url"
value="<?php echo esc_attr( $server_url ); ?>"
class="regular-text" placeholder="https://example.com">
<p class="description"><?php esc_html_e( 'The URL of the license server.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_license_key"><?php esc_html_e( 'License Key', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_license_key" id="wp_bnb_license_key"
value="<?php echo esc_attr( $license_key ); ?>"
class="regular-text" placeholder="XXXX-XXXX-XXXX-XXXX">
<p class="description"><?php esc_html_e( 'Your license key from the purchase confirmation email.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_license_server_secret"><?php esc_html_e( 'Server Secret', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="password" name="wp_bnb_license_server_secret" id="wp_bnb_license_server_secret"
value="" class="regular-text"
placeholder="<?php echo ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : ''; ?>">
<p class="description"><?php esc_html_e( 'Leave empty to keep the current secret. The shared secret for secure communication.', 'wp-bnb' ); ?></p>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save License Settings', 'wp-bnb' ), 'primary', 'submit', false ); ?>
<button type="button" id="wp-bnb-validate-license" class="button button-secondary">
<?php esc_html_e( 'Validate License', 'wp-bnb' ); ?>
</button>
<button type="button" id="wp-bnb-activate-license" class="button button-secondary">
<?php esc_html_e( 'Activate License', 'wp-bnb' ); ?>
</button>
<span class="spinner" id="wp-bnb-license-spinner"></span>
</p>
<div id="wp-bnb-license-message" style="display: none;"></div>
</form>
<?php
}
/**
* Render license status badge.
*
* @param string $status License status.
* @return void
*/
private function render_license_status_badge( string $status ): void {
$badges = array(
'valid' => array(
'class' => 'dashicons-yes-alt',
'color' => '#00a32a',
'label' => __( 'Valid', 'wp-bnb' ),
),
'invalid' => array(
'class' => 'dashicons-dismiss',
'color' => '#d63638',
'label' => __( 'Invalid', 'wp-bnb' ),
),
'expired' => array(
'class' => 'dashicons-warning',
'color' => '#dba617',
'label' => __( 'Expired', 'wp-bnb' ),
),
'revoked' => array(
'class' => 'dashicons-dismiss',
'color' => '#d63638',
'label' => __( 'Revoked', 'wp-bnb' ),
),
'inactive' => array(
'class' => 'dashicons-marker',
'color' => '#72aee6',
'label' => __( 'Inactive', 'wp-bnb' ),
),
'unchecked' => array(
'class' => 'dashicons-info',
'color' => '#72aee6',
'label' => __( 'Not checked', 'wp-bnb' ),
),
'unconfigured' => array(
'class' => 'dashicons-admin-generic',
'color' => '#646970',
'label' => __( 'Not configured', 'wp-bnb' ),
),
);
$badge = $badges[ $status ] ?? $badges['unconfigured'];
?>
<span class="wp-bnb-license-status" style="color: <?php echo esc_attr( $badge['color'] ); ?>;">
<span class="dashicons <?php echo esc_attr( $badge['class'] ); ?>"></span>
<?php echo esc_html( $badge['label'] ); ?>
</span>
<?php
}
/**
* Save settings based on active tab.
*
* @param string $tab Active tab.
* @return void
*/
private function save_settings( string $tab ): void {
switch ( $tab ) {
case 'license':
$this->save_license_settings();
break;
default:
$this->save_general_settings();
break;
}
}
/**
* Save general settings.
*
* @return void
*/
private function save_general_settings(): void {
if ( isset( $_POST['wp_bnb_business_name'] ) ) {
update_option( 'wp_bnb_business_name', sanitize_text_field( wp_unslash( $_POST['wp_bnb_business_name'] ) ) );
}
if ( isset( $_POST['wp_bnb_currency'] ) ) {
update_option( 'wp_bnb_currency', sanitize_text_field( wp_unslash( $_POST['wp_bnb_currency'] ) ) );
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save license settings.
*
* @return void
*/
private function save_license_settings(): void {
$data = array(
'license_key' => isset( $_POST['wp_bnb_license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_key'] ) ) : '',
'server_url' => isset( $_POST['wp_bnb_license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['wp_bnb_license_server_url'] ) ) : '',
'server_secret' => isset( $_POST['wp_bnb_license_server_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_server_secret'] ) ) : '',
);
LicenseManager::save_settings( $data );
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'License settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Get Twig environment.
*
* @return Environment
*/
public function get_twig(): Environment {
if ( null === $this->twig ) {
$loader = new FilesystemLoader( WP_BNB_PATH . 'templates' );
$this->twig = new Environment(
$loader,
array(
'cache' => WP_DEBUG ? false : WP_BNB_PATH . 'cache/twig',
'debug' => WP_DEBUG,
)
);
}
return $this->twig;
}
/**
* Render a Twig template.
*
* @param string $template Template name.
* @param array $context Template context.
* @return string
*/
public function render( string $template, array $context = array() ): string {
return $this->get_twig()->render( $template, $context );
}
}